2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

開発中のSNSのOGP画像自動生成をCloud Functionsで実装する

Last updated at Posted at 2020-02-09

ogp_1130210.png

前から「Snovel」という駅ノートでつながるSNSを作ってます。
あわせてLP(https://snovel.app)も作りました。
アプリから駅にチェックインしたことがシェアされたときに、共通の画像だとちょっとさみしいので「〜駅でチェックインしよう」とチェックインされた駅に合わせてLPのOGP画像を自動生成されるような仕組みを作ってみました。
実際これを使うにはSSR等の技術が必要になるかと思いますが、SSRについては他の記事を読んでください。

対象読者

  • フロントエンド中級者
  • Firebase、Cloud Functionsが何か知ってる人

使うもの

  • Cloud Functions for Firebase
  • 生成する画像の元画像(例えば下のようなもの)
    ogpbase.png
  • (必要に応じて)Firebaseの課金プラン
  • 画像に書き込むフォント(TTF,OTF)

Functionsでエンドポイントを作る

アプリのチェックイン時やLP表示時に叩くために、OGP生成関数のエンドポイントを作ります。

index.js
const functions = require("firebase-functions");

// 他の関数もあるので、OGPの関数が呼び出されたときだけ読み込むようにする
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'ogp') {
    exports.ogp = functions
    .region('asia-northeast1') // 特に使わない理由がないので東京リージョン
    .https
    .onRequest(require('./ogp')); // onCallとかを使うとCORS対策にもなるらしけどやったことがない
}
ogp.js
const admin = require("firebase-admin");
const spawn = require("child-process-promise").spawn;
const path = require("path");
const os = require("os");
const admin = require("firebase-admin");

admin.initializeApp();

// ご自身で用意してください
const FONT_NOTO = "fonts/NotoSansCJKjp-Bold.otf";

const ogp = async (req, res) => {
    // CORS対策
    res.set('Access-Control-Allow-Origin', '*');
    if (req.method === 'OPTIONS') {
        res.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
        res.set('Access-Control-Allow-Headers', 'Content-Type');
        res.set('Access-Control-Max-Age', '3600');
        res.status(204).send('');
        return
    }

    // 必要な変数を定義する
    const bucket = admin.storage().bucket();
    const STORAGE_ROOT = "https://firebasestorage.googleapis.com/v0/b";
    const bucketName = bucket.name;
    const uploadPath = 'ogp/PUT_ID_HERE.png';
    const dlPath = encodeURIComponent(uploadPath);
    const dlURL = `${STORAGE_ROOT}/${bucketName}/o/${dlPath}?alt=media`;

    // すでに生成されたファイルがある場合はそのままそれを返す
    const uploadedFile = bucket.file(uploadPath);
    const alreadyExists = await uploadedFile.exists();
    if (alreadyExists[0]) {
        res.send(dlURL);
        return;
    }

    // 実際画像に出てくる文言。実際はAPIから取ってきたのを入れたりとか。
    const msg = '高田馬場駅にチェックインしよう';
    // ベースの画像
    const template = 'img/ogpbase.png';
    // 生成した画像のパス。tmpディレクトリに配置する
    const outFile = path.join(os.tmpdir(), 'generated.png');

    // ImageMagicで画像生成
    await spawn("convert", [
        "-font", FONT_NOTO, // フォントの指定。カスタムフォントの場合はパスを指定
        "-pointsize", "48", // フォントサイズの指定
        "-fill", "white", // 文字色の指定。白文字に設定
        "-gravity", "Center", // 位置の基準を指定。
        "-annotate", "+0-16", msg, // 文字の指定。+0+0は位置の基準からの相対位置。
        template, // 入力画像のパス
        outFile // 出力画像のパス
    ]);

    // Cloud Storageへのアップロード
    await bucket.upload(outFile, {
        destination: uploadPath
    });

    // return Download URL
    res.status(201).send(dlURL);
}
module.exports = ogp;

割と簡単でしたね。

実際のところは、https://snovel.app/station/1130210のようなURLにアクセスされたり、アプリでチェックインされたときに上記のAPIが叩かれて、シェアするときに一緒にOGPとしてシェアされる感じです。

注意点

割と重い処理なので、関数のメモリ割り当てを上げたり、1200x630を超えるような画像を扱わないようにしてください。

リポジトリ

SnovelLP

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?