Help us understand the problem. What is going on with this article?

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

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

TinyKitten
ぶっちゃけプログラミングより猫のほうが好き
https://tinykitten.me
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした