Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What is going on with this article?
@TinyKitten

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

More than 1 year has passed since last update.

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

0
Help us understand the problem. What is going on with this article?
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
TinyKitten
ぶっちゃけプログラミングより猫のほうが好き

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
0
Help us understand the problem. What is going on with this article?