🐭

その日のコミット数に応じて伸びる Gopher くん svg

2022/06/06に公開

Github の README でよく見るオシャレなやつを作りたくなったので、 Gopher くんで遊ぶ。

必要な機能

  • コミット数の取得
  • <svg>を返す

今回は Python で FastAPI を使って、Heroku にデプロイして作る。

コミット数の取得はこちらのサイトを参考に、Curl Converterでサクッと変換する。

その結果、以下のコードで最も新しい日付のコミット数を取得できる。
リテラルが多いですが...

import json
import os

from dotenv import load_dotenv
import requests


load_dotenv()
TOKEN = os.getenv("TOKEN")
username = "ogty"

headers = {
  'Authorization': f'bearer {TOKEN}',
  'Content-Type': 'application/json',
}

query = """
query($userName:String!) {
  user(login: $userName){
    contributionsCollection {
      contributionCalendar {
        totalContributions
        weeks {
          contributionDays {
            color
            contributionCount
            contributionLevel
            date
          }
        }
      }
    }
  }
}
"""
variables = '{"userName": "%s"}' % username

body = {
  "query": query,
  "variables": variables
 }
response = requests.post(
  'https://api.github.com/graphql', 
  headers=headers, 
  data=json.dumps(body)
)
json_data = json.loads(response.text)
todays_contributions = json_data["data"]["user"]["contributionsCollection"]["contributionCalendar"]["weeks"][-1]["contributionDays"][-1]["contributionCount"]

事前に Github の設定画面から Token を取得しておき.envファイルに記述する(参考サイト参照)。

TOKEN=xxxxxxxxxxxxx

適当に拾った Gopher くんの画像データを、体を伸ばす棒を入れる良さげな場所で切断し、二つの svg データとして保存する。

<svg xmlns="http://www.w3.org/2000/svg" height="420" width="180" viewbox="0 0 420 500">
  <g transform="translate(0,175) scale(0.05,-0.05)">
    <path d="M1595 3460 c-169 -12 -315 -40 -467 -91 -48 -16 -93 -29 -99 -29 -27 0 -269 -129 -306 -162 -18 -17 -22 -16 -74 4 -84 33 -224 31 -303 -5 -160 -73 -257 -223 -259 -402 -1 -63 4 -91 21 -129 30 -67 82 -128 138 -162 l45 -29 -4 -70 c-2 -38 -10 -141 -17 -228 -10 -125 -11 -198 -1 -360 7 -111 16 -276 21 -367 5 -91 12 -192 16 -226 l6 -60 -58 -13 c-111 -25 -205 -85 -241 -154 -37 -72 11 -262 76 -300 35 -21 111 -22 166 -2 21 8 40 13 42 12 1 -1 -2 -83 -7 -182 -5 -99 -13 -253 -16 -342 l-7 -163 46 0 47 0 0 133 c0 72 10 249 22 392 24 293 28 636 10 840 -39 440 -44 537 -39 725 16 575 196 922 583 1120 148 76 405 144 636 169 124 14 376 14 509 1 235 -24 443 -87 588 -177 165 -103 312 -280 378 -454 106 -284 139 -543 148 -1179 3 -228 14 -525 23 -660 11 -155 17 -367 17 -578 l0 -332 42 0 41 0 8 88 c4 48 4 202 0 343 l-7 257 67 -6 c89 -7 125 12 159 84 30 64 44 179 25 223 -26 63 -129 133 -237 161 l-43 11 0 317 c0 442 -19 715 -65 936 -8 39 -15 76 -15 82 0 7 22 26 50 44 52 33 110 103 135 164 10 23 15 70 15 139 0 100 -1 106 -39 183 -98 201 -293 282 -501 210 l-69 -25 -33 24 c-62 45 -133 85 -188 105 -30 11 -64 25 -75 30 -96 47 -288 81 -510 90 -227 10 -248 10 -400 0z m1540 -304 c67 -30 115 -74 148 -134 77 -141 48 -286 -75 -370 -74 -51 -76 -50 -114 62 l-34 99 21 34 c27 43 19 91 -20 135 -23 26 -35 31 -77 32 -46 1 -52 4 -102 58 l-54 57 42 21 c87 44 176 46 265 6z m-2560 -37 c22 -6 55 -18 73 -26 l33 -16 -48 -51 c-27 -28 -58 -63 -70 -79 -18 -24 -24 -26 -48 -17 -51 20 -104 12 -135 -18 -40 -41 -48 -92 -20 -137 12 -19 25 -35 28 -35 4 0 15 -6 24 -14 16 -12 15 -17 -6 -84 -12 -40 -25 -75 -29 -78 -12 -13 -98 39 -136 83 -51 57 -64 98 -59 181 11 193 210 341 393 291z m2822 -2079 c63 -31 103 -77 103 -117 0 -30 -10 -34 -41 -18 -35 19 -39 19 -39 0 0 -9 11 -21 25 -27 28 -12 30 -20 15 -49 -16 -29 -76 -25 -147 12 -60 31 -61 31 -67 83 -3 28 -6 75 -6 105 l0 54 48 -7 c26 -3 75 -20 109 -36z m-3018 -27 c0 -27 -4 -68 -7 -92 -7 -47 -3 -43 -132 -112 -42 -23 -88 -24 -112 -2 -25 22 -23 38 7 51 14 6 25 18 25 27 0 17 -25 21 -35 5 -3 -5 -15 -10 -26 -10 -14 0 -19 7 -19 25 0 75 126 153 248 155 l52 0 -1 -47z"/>
    <path d="M2140 3148 c-129 -45 -238 -139 -289 -248 -22 -48 -25 -69 -26 -165 0 -101 2 -115 28 -168 88 -179 298 -276 508 -234 262 51 407 237 371 474 -7 42 -23 101 -37 130 -34 76 -115 157 -194 196 -61 29 -75 32 -181 34 -99 3 -124 0 -180 -19z m245 -9 c203 -37 336 -213 323 -424 -13 -198 -174 -342 -403 -362 -272 -23 -498 192 -455 434 16 93 45 149 115 219 113 114 265 162 420 133z"/>
    <path d="M1992 2840 c-94 -57 -75 -230 28 -261 47 -14 79 -7 119 27 76 64 60 193 -29 239 -40 21 -79 19 -118 -5z"/>
    <path d="M1073 3115 c-140 -30 -277 -132 -323 -240 -85 -199 -10 -441 168 -538 182 -99 443 -65 590 76 198 191 146 538 -98 662 -101 51 -221 65 -337 40z m226 -30 c199 -47 312 -202 299 -409 -15 -238 -242 -399 -513 -364 -97 12 -181 55 -244 123 -70 78 -95 145 -95 265 -1 84 3 102 28 157 82 179 311 278 525 228z"/>
    <path d="M884 2801 c-36 -22 -64 -75 -64 -121 0 -117 130 -183 215 -109 68 60 68 158 0 218 -43 37 -102 42 -151 12z"/>
    <path d="M1700 2451 c-72 -22 -130 -77 -130 -122 0 -8 -18 -23 -39 -33 -84 -36 -134 -132 -111 -210 14 -46 60 -96 89 -96 18 0 21 -6 21 -45 0 -126 50 -215 120 -215 27 0 48 8 69 26 l31 26 31 -26 c39 -34 70 -41 112 -27 63 21 72 41 71 152 l-2 99 43 0 c82 0 153 80 139 156 -8 45 -68 114 -130 150 -32 19 -43 32 -48 59 -8 39 -51 85 -98 102 -37 13 -130 15 -168 4z m-105 -195 c26 -24 99 -46 153 -46 45 0 142 29 174 52 28 20 32 20 64 5 50 -24 112 -84 124 -121 13 -40 1 -76 -36 -111 -35 -33 -78 -32 -157 1 -91 39 -165 41 -256 10 -99 -35 -154 -34 -186 0 -37 40 -42 85 -14 141 21 42 76 93 100 93 4 0 20 -11 34 -24z m142 -332 c-3 -113 -4 -118 -31 -140 -33 -29 -77 -31 -100 -5 -34 38 -59 166 -38 198 6 10 135 58 165 62 4 1 6 -51 4 -115z m154 92 c36 -15 36 -16 43 -91 10 -107 2 -140 -39 -159 -46 -22 -59 -20 -94 15 -28 28 -31 37 -31 92 0 34 3 86 7 116 l6 53 36 -5 c20 -3 52 -12 72 -21z"/>
  </g>
  <rect x="13.4" y="175" width="5" height="0"/>
  <rect x="161.5" y="175" width="4.5" height="0"/>
  <g transform="translate(-0.1,238) scale(0.05,-0.05)">
    <path d="M280 1323 c0 -80 23 -317 37 -378 17 -74 95 -282 122 -325 8 -14 39 -55 67 -93 l52 -68 -64 -61 c-123 -117 -139 -265 -42 -366 l32 -32 130 0 131 0 50 45 c63 57 132 104 164 112 15 4 61 -6 120 -25 344 -113 776 -131 1201 -52 91 17 243 66 319 103 l64 31 81 -86 c112 -119 128 -128 216 -128 66 0 74 2 118 36 110 83 128 161 68 290 -19 40 -53 93 -75 117 l-41 44 57 84 c62 93 107 185 153 316 39 111 47 143 65 283 8 63 17 130 20 148 l6 33 -47 -3 -48 -3 -17 -105 c-59 -373 -192 -625 -416 -794 -253 -191 -599 -286 -1040 -286 -248 0 -441 29 -640 95 -479 160 -712 471 -749 999 l-7 96 -43 0 c-41 0 -44 -2 -44 -27z m2700 -904 c97 -95 133 -199 84 -240 -8 -6 -15 -10 -17 -8 -1 2 -15 31 -32 63 -16 33 -36 62 -42 64 -18 5 -16 -33 1 -47 8 -7 22 -33 30 -58 20 -57 9 -73 -47 -73 -38 0 -45 6 -143 108 -57 59 -104 110 -104 114 0 4 8 9 18 12 9 3 51 33 92 65 41 33 80 60 87 60 6 1 39 -26 73 -60z m-2218 -21 c41 -29 97 -64 126 -77 29 -14 52 -27 52 -31 0 -3 -16 -12 -35 -19 -20 -7 -75 -44 -123 -83 -134 -108 -183 -127 -223 -87 -24 24 -20 40 23 109 17 27 27 53 23 59 -10 17 -22 5 -61 -65 -41 -74 -54 -73 -61 3 -4 42 -1 54 26 90 22 31 163 151 178 153 1 0 35 -24 75 -52z"/>
  </g>
</svg>

あとは FastAPI に合わせる

import json
import os
from string import hexdigits

from dotenv import load_dotenv
import requests
from fastapi import FastAPI, Response


load_dotenv()
app = FastAPI()
TOKEN = os.getenv('TOKEN')


@app.get("/")
async def today(username: str, color: str = "#75ccde"):
    headers = {
        'Authorization': f'bearer {TOKEN}',
        'Content-Type': 'application/json',
    }

    query = """
    query($userName:String!) {
      user(login: $userName){
        contributionsCollection {
          contributionCalendar {
            totalContributions
            weeks {
              contributionDays {
                color
                contributionCount
                contributionLevel
                date
              }
            }
          }
        }
      }
    }
    """
    variables = '{"userName": "%s"}' % username

    body = {
        "query": query,
        "variables": variables
    }
    response = requests.post(
        'https://api.github.com/graphql', 
        headers=headers, 
        data=json.dumps(body)
    )
    json_data = json.loads(response.text)
    todays_contributions = json_data["data"]["user"]["contributionsCollection"]["contributionCalendar"]["weeks"][-1]["contributionDays"][-1]["contributionCount"]

    height = todays_contributions * 10
    default_height = 238
    svg_height = height + default_height + 175

    content = f"""
    <svg xmlns="http://www.w3.org/2000/svg" height="{svg_height}" width="180" viewbox="0 0 {svg_height} 500" fill="{ f'#{color}' if all(c in hexdigits for c in color) else color}">
        <g transform="translate(0,175) scale(0.05,-0.05)">
            <path d="M1595 3460 c-169 -12 -315 -40 -467 -91 -48 -16 -93 -29 -99 -29 -27 0 -269 -129 -306 -162 -18 -17 -22 -16 -74 4 -84 33 -224 31 -303 -5 -160 -73 -257 -223 -259 -402 -1 -63 4 -91 21 -129 30 -67 82 -128 138 -162 l45 -29 -4 -70 c-2 -38 -10 -141 -17 -228 -10 -125 -11 -198 -1 -360 7 -111 16 -276 21 -367 5 -91 12 -192 16 -226 l6 -60 -58 -13 c-111 -25 -205 -85 -241 -154 -37 -72 11 -262 76 -300 35 -21 111 -22 166 -2 21 8 40 13 42 12 1 -1 -2 -83 -7 -182 -5 -99 -13 -253 -16 -342 l-7 -163 46 0 47 0 0 133 c0 72 10 249 22 392 24 293 28 636 10 840 -39 440 -44 537 -39 725 16 575 196 922 583 1120 148 76 405 144 636 169 124 14 376 14 509 1 235 -24 443 -87 588 -177 165 -103 312 -280 378 -454 106 -284 139 -543 148 -1179 3 -228 14 -525 23 -660 11 -155 17 -367 17 -578 l0 -332 42 0 41 0 8 88 c4 48 4 202 0 343 l-7 257 67 -6 c89 -7 125 12 159 84 30 64 44 179 25 223 -26 63 -129 133 -237 161 l-43 11 0 317 c0 442 -19 715 -65 936 -8 39 -15 76 -15 82 0 7 22 26 50 44 52 33 110 103 135 164 10 23 15 70 15 139 0 100 -1 106 -39 183 -98 201 -293 282 -501 210 l-69 -25 -33 24 c-62 45 -133 85 -188 105 -30 11 -64 25 -75 30 -96 47 -288 81 -510 90 -227 10 -248 10 -400 0z m1540 -304 c67 -30 115 -74 148 -134 77 -141 48 -286 -75 -370 -74 -51 -76 -50 -114 62 l-34 99 21 34 c27 43 19 91 -20 135 -23 26 -35 31 -77 32 -46 1 -52 4 -102 58 l-54 57 42 21 c87 44 176 46 265 6z m-2560 -37 c22 -6 55 -18 73 -26 l33 -16 -48 -51 c-27 -28 -58 -63 -70 -79 -18 -24 -24 -26 -48 -17 -51 20 -104 12 -135 -18 -40 -41 -48 -92 -20 -137 12 -19 25 -35 28 -35 4 0 15 -6 24 -14 16 -12 15 -17 -6 -84 -12 -40 -25 -75 -29 -78 -12 -13 -98 39 -136 83 -51 57 -64 98 -59 181 11 193 210 341 393 291z m2822 -2079 c63 -31 103 -77 103 -117 0 -30 -10 -34 -41 -18 -35 19 -39 19 -39 0 0 -9 11 -21 25 -27 28 -12 30 -20 15 -49 -16 -29 -76 -25 -147 12 -60 31 -61 31 -67 83 -3 28 -6 75 -6 105 l0 54 48 -7 c26 -3 75 -20 109 -36z m-3018 -27 c0 -27 -4 -68 -7 -92 -7 -47 -3 -43 -132 -112 -42 -23 -88 -24 -112 -2 -25 22 -23 38 7 51 14 6 25 18 25 27 0 17 -25 21 -35 5 -3 -5 -15 -10 -26 -10 -14 0 -19 7 -19 25 0 75 126 153 248 155 l52 0 -1 -47z"/>
            <path d="M2140 3148 c-129 -45 -238 -139 -289 -248 -22 -48 -25 -69 -26 -165 0 -101 2 -115 28 -168 88 -179 298 -276 508 -234 262 51 407 237 371 474 -7 42 -23 101 -37 130 -34 76 -115 157 -194 196 -61 29 -75 32 -181 34 -99 3 -124 0 -180 -19z m245 -9 c203 -37 336 -213 323 -424 -13 -198 -174 -342 -403 -362 -272 -23 -498 192 -455 434 16 93 45 149 115 219 113 114 265 162 420 133z"/>
            <path d="M1992 2840 c-94 -57 -75 -230 28 -261 47 -14 79 -7 119 27 76 64 60 193 -29 239 -40 21 -79 19 -118 -5z"/>
            <path d="M1073 3115 c-140 -30 -277 -132 -323 -240 -85 -199 -10 -441 168 -538 182 -99 443 -65 590 76 198 191 146 538 -98 662 -101 51 -221 65 -337 40z m226 -30 c199 -47 312 -202 299 -409 -15 -238 -242 -399 -513 -364 -97 12 -181 55 -244 123 -70 78 -95 145 -95 265 -1 84 3 102 28 157 82 179 311 278 525 228z"/>
            <path d="M884 2801 c-36 -22 -64 -75 -64 -121 0 -117 130 -183 215 -109 68 60 68 158 0 218 -43 37 -102 42 -151 12z"/>
            <path d="M1700 2451 c-72 -22 -130 -77 -130 -122 0 -8 -18 -23 -39 -33 -84 -36 -134 -132 -111 -210 14 -46 60 -96 89 -96 18 0 21 -6 21 -45 0 -126 50 -215 120 -215 27 0 48 8 69 26 l31 26 31 -26 c39 -34 70 -41 112 -27 63 21 72 41 71 152 l-2 99 43 0 c82 0 153 80 139 156 -8 45 -68 114 -130 150 -32 19 -43 32 -48 59 -8 39 -51 85 -98 102 -37 13 -130 15 -168 4z m-105 -195 c26 -24 99 -46 153 -46 45 0 142 29 174 52 28 20 32 20 64 5 50 -24 112 -84 124 -121 13 -40 1 -76 -36 -111 -35 -33 -78 -32 -157 1 -91 39 -165 41 -256 10 -99 -35 -154 -34 -186 0 -37 40 -42 85 -14 141 21 42 76 93 100 93 4 0 20 -11 34 -24z m142 -332 c-3 -113 -4 -118 -31 -140 -33 -29 -77 -31 -100 -5 -34 38 -59 166 -38 198 6 10 135 58 165 62 4 1 6 -51 4 -115z m154 92 c36 -15 36 -16 43 -91 10 -107 2 -140 -39 -159 -46 -22 -59 -20 -94 15 -28 28 -31 37 -31 92 0 34 3 86 7 116 l6 53 36 -5 c20 -3 52 -12 72 -21z"/>
        </g>
        <rect x="13.4" y="175" width="5" height="{height}"/>
        <rect x="161.5" y="175" width="4.5" height="{height}"/>
        <g transform="translate(-0.1,{default_height + height}) scale(0.05,-0.05)">
            <path d="M280 1323 c0 -80 23 -317 37 -378 17 -74 95 -282 122 -325 8 -14 39 -55 67 -93 l52 -68 -64 -61 c-123 -117 -139 -265 -42 -366 l32 -32 130 0 131 0 50 45 c63 57 132 104 164 112 15 4 61 -6 120 -25 344 -113 776 -131 1201 -52 91 17 243 66 319 103 l64 31 81 -86 c112 -119 128 -128 216 -128 66 0 74 2 118 36 110 83 128 161 68 290 -19 40 -53 93 -75 117 l-41 44 57 84 c62 93 107 185 153 316 39 111 47 143 65 283 8 63 17 130 20 148 l6 33 -47 -3 -48 -3 -17 -105 c-59 -373 -192 -625 -416 -794 -253 -191 -599 -286 -1040 -286 -248 0 -441 29 -640 95 -479 160 -712 471 -749 999 l-7 96 -43 0 c-41 0 -44 -2 -44 -27z m2700 -904 c97 -95 133 -199 84 -240 -8 -6 -15 -10 -17 -8 -1 2 -15 31 -32 63 -16 33 -36 62 -42 64 -18 5 -16 -33 1 -47 8 -7 22 -33 30 -58 20 -57 9 -73 -47 -73 -38 0 -45 6 -143 108 -57 59 -104 110 -104 114 0 4 8 9 18 12 9 3 51 33 92 65 41 33 80 60 87 60 6 1 39 -26 73 -60z m-2218 -21 c41 -29 97 -64 126 -77 29 -14 52 -27 52 -31 0 -3 -16 -12 -35 -19 -20 -7 -75 -44 -123 -83 -134 -108 -183 -127 -223 -87 -24 24 -20 40 23 109 17 27 27 53 23 59 -10 17 -22 5 -61 -65 -41 -74 -54 -73 -61 3 -4 42 -1 54 26 90 22 31 163 151 178 153 1 0 35 -24 75 -52z"/>
        </g>
    </svg>
    """

    return Response(content=content, media_type="image/svg+xml")

伸ばす長さはコミット数 × 10で設定。

引数にはusernameでGithubのアカウント名を、colorfillする色をクエリ文字パラメータに取る。

デプロイ

デプロイはこちらのサイトを参考に。

$ pip3 install fastapi "uvicorn[standard]"
$ uvicorn main:app --reload
$ brew tap heroku/brew && brew install heroku
$ heroku --version
$ heroku login
$ heroku create <app-name>
$ git init
$ git add .
$ git commit -m "<commit-message>"
$ heroku git:remote -a <app-name>
$ git push heroku master

完成

Discussion