notebook

都内でWEB系エンジニアやってます。

DenoとCanvasでGitHubのアクティビティを画像化して返すAPIを作った

DenoとCanvasを使ってGitHubのアクティビティ(草)の画像を生成するプロジェクトを作った

swfz/deno-kusa-image: GitHub Contribution Image in deno

github.com

こんな感じでGitHubの草を画像化したレスポンスを用意するくん(パラメータとかは執筆時のものなので今後変わる可能性はある)

https://kusa-image.deno.dev/?user=swfz

alt

userを指定するだけ

やっていること

  • Deno.serveでサーバを立てる
  • GitHubのAPIをたたく
  • Canvasでレンダリングし、画像レスポンスにする

server

Deno.serveを使った

Deno.serve | Runtime APIs | Deno

deno.land

ドキュメントみながら書くだけでOK

GitHubのAPI

GraphQLのAPIでContribution情報が取得できるのでここから取得している

ドキュメントは下記参照すれば分かる

GitHub GraphQL API に関するドキュメント - GitHub Docs

docs.github.com

オブジェクト - GitHub Docs

docs.github.com

あとはExplorerで実行しながらみてみると良い

次のようなクエリで

user名を引数で渡すとそのユーザーに関するContributionのデータを取得できる

query($userName:String!) {
  user(login: $userName){
    contributionsCollection {
      contributionCalendar {
        totalContributions
        weeks {
          contributionDays {
            color
            contributionCount
            contributionLevel
            date
          }
        }
      }
    }
  }
}

コードに落とし込むと次のような感じ

const token = Deno.env.get("GH_READ_USER_TOKEN");
const query = `
  query($user:String!) {
    user(login: $user){
      contributionsCollection {
        contributionCalendar {
          totalContributions
          weeks {
            contributionDays {
              color
              contributionCount
              contributionLevel
              date
            }
          }
        }
      }
    }
  }
`;

const variables = { user };
const json = { query, variables };
const url = "https://api.github.com/graphql";
const res = await fetch(url, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
  body: JSON.stringify(json),
});

res.json();

Canvas

Denoでcanvasを扱うためのライブラリ

canvas@v1.4.1 | Deno

https://deno.land/x/canvas@v1.4.1deno.land

使い方は通常のCanvasの使い方と同様、一部対応してないメソッドなどもあるが基本的なものは使えるはず

GitHubのAPIの情報をもとにレンダリングするだけ

コードは少し長くなってしまうので載せないが下記にある

deno-kusa-image/renderCanvas.ts at main · swfz/deno-kusa-image

最後にcanvas.toBuffer()でレスポンスに指定するだけで画像としてレスポンスを返してくれる

Canvasで何かしらレンダリングして画像としてレスポンスを返すサンプル

  • server.ts
import { createCanvas } from "https://deno.land/x/canvas/mod.ts";

const port = 8080;
const handler = (request: Request): Response => {
  const canvas = createCanvas(200, 200);
  const ctx = canvas.getContext("2d");

  ctx.fillStyle = "red";
  ctx.fillRect(10, 10, 200 - 20, 200 - 20);

  const headers = new Headers();
  headers.set("content-type", "image/png");

  return new Response(canvas.toBuffer(), {headers: headers, status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
Deno.serve({ port }, handler);

これだけで画像としてのレスポンスを返せる

開発とデプロイ

  • ローカルでの開発(run)
deno run --allow-net --allow-env server.ts

APIへの接続と環境変数にGitHubのGraphQLAPIへのアクセスキーが必要なため--allow-net,allow-envを指定

  • デプロイ(deployctl)

deployは事前にDenoアカウントを作っておく必要はあるがめちゃくちゃ簡単だった

deployctl deploy --project=kusa-image --prod server.ts

--projectは必須、一意の文字列にする必要がある

--prodをつけないとプレビュー用にデプロイできるのでそこで確認してproductionデプロイといった手順を踏むこともできる

まとめと感想

  • DenoとCanvasを使ってGitHubのアクティビティを画像化するサーバを作った
    • 画像なのでOGPに使うことができる
  • この組み合わせに限らず、パラメータをもとに動的に画像生成するのは色々準備が必要でたいへんなイメージだったが、この組み合わせだとさくっとできた
    • 色々できそうだなと感じたので他にも作ってみたい
  • ContributionAPIがアクティビティに関する情報をほとんど持っているので、取得したレスポンスをそのまま使ってCanvasのレンダリングするだけでOKで楽
    • RGBまで返ってくるのでカスタマイズしなくてよいのなら色指定も不要だった