はじめに
Notion上でGitHubの草(コントリビューション)を確認できると便利だと思い、ウィジェットを作成してみました。
作ったもの
デモページになります。
実装
1. GitHubのコントリビューションを取得する
GitHubのコントリビューション数を取得するため、以下のURLからスクレイピングを行います。
https://github.com/users/<UserName>/contributions
HTML構造は以下のようになっていました。
<td tabindex="0"
data-ix="{id?}"
aria-selected="false"
aria-describedby="contribution-graph-legend-level-{level}"
style="width: 10px"
data-date="{date}"
id="contribution-day-component-{row}-{col}"
data-level="{contribution-level}"
role="gridcell"
data-view-component="true"
class="ContributionCalendar-day">
</td>
<tool-tip id="tooltip-{id}"
for="contribution-day-component-{row}-{col}"
popover="manual"
data-direction="n"
data-type="label"
data-view-component="true"
class="sr-only position-absolute">
{contribution-text}
</tool-tip>
これらから
-
<td>
要素のdata-date
-
<tool-tip>
要素の{contribution-text}
の整数部分
を取得します。特に、{contribution-text}
はその日のコントリビューションがないとNo contributions on {month} {day}.
となるので場合分けします。また、日付の順番がぐちゃぐちゃなので、日付について昇順でソートします。
以下にPythonコードを示します。
import csv
import requests
from bs4 import BeautifulSoup
user_name = 'username'
url = "https://github.com/users/" + user_name + "/contributions"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
days = soup.find_all("td", {"class": "ContributionCalendar-day"})
contributions = []
for day in days:
date = day.get("data-date")
tooltip = day.find_next("tool-tip")
if tooltip:
tooltip_text = tooltip.text.strip()
if "No contributions" in tooltip_text:
cnt = 0
else:
cnt = int(tooltip_text.split()[0])
else:
cnt = 0
if date:
contributions.append((date, cnt))
contributions.sort(key=lambda x: x[0])
2. Webサイトを作成する
コントリビューショングラフの色分けには以下の基準を使用します。
- レベル0:コントリビューションなし
- レベル1: $(0, \frac{最大値}{4}]$
- レベル2: $(\frac{最大値}{4}, \frac{最大値}{2}]$
- レベル3: $(\frac{最大値}{2}, \frac{3 *最大値}{4}]$
- レベル4: $(\frac{3*最大値}{4},最大値]$
また、描画する日数を計算しなければいけません。例えば今日が月曜日だとすると7×6+2で合計44日間のデータが必要です。
datetime
モジュールのweekday
メソッドを使うと日付の曜日を数値で取得することができます。月曜日の場合は0を返すので工夫しました。
以下にFlaskの処理例を示します。
@app.route('/')
def index():
today = datetime.now()
# 曜日
if today.weekday() == 0:
dow = 5
elif today.weekday() == 6:
dow = 6
else:
dow = 5 % today.weekday()
size = 49 # 7の倍数
contributions, _ = load_csv(file_path)
# カレンダー
calendar_data = []
for i in range(size - dow):
day = today - timedelta(days=size - dow - i - 1)
date_str = day.strftime("%Y-%m-%d")
count = contributions.get(date_str, 0)
calendar_data.append({"day": day.day, "color": get_color(count)})
show_data = [[] for _ in range(len(calendar_data) // 7 + 1)]
for i in range(len(calendar_data)):
show_data[i // 7].append(calendar_data[i])
return render_template('index.html',calendar=show_data)
続いてフロント側の処理も書いていきます。このとき以下のようにWebサイトの中央にウィジェットとして表示したいものが来るようにします。
コントリビューションレベルに応じたカラーコードは以下の通りです。
レベル | カラーコード |
---|---|
0 | #2b2b2b |
1 | #0e4429 |
2 | #006d32 |
3 | #26a641 |
4 | #39d353 |
また、Notionの背景色とそろえておく必要があります。
モード | カラーコード |
---|---|
ライト | #ffffff |
ダーク | #191919 |
3. AzureWebAppsでデプロイする
今回、Azureにデプロイする際にGitHub Actionsを使いたいのでGitHubにコードを上げます。アプリケーションのコードを以下のような構成で整理し、GitHubにアップロードします。このとき、app.py
(またはapplication.py
)とrequirements.txt
はルートディレクトリに置かなければいけません。HTMLはtemplates
フォルダに、CSSはstatic
フォルダに置きます。
project/
├── app.py # メインのアプリケーションコード
├── requirements.txt # 必要なPythonライブラリを記載
├── static/ # CSSや画像など静的ファイル
│ ├── styles.css
├── templates/ # HTMLファイル
│ ├── index.html
└── .github/
└── workflows/ # GitHub Actionsの設定ファイル
└── deploy.yml
Azureポータルにアクセスし、必要であればリソースグループを新規作成します。リソースグループは、アプリケーションや関連リソースをまとめて管理するための単位です。
App ServiseからWebアプリを新規作成します。ランタイムスタックにPythonを指定します。デプロイの項目は作成完了後に設定が可能になります。
作成後、デプロイセンターに移動します。デプロイオプションでGitHubを選択し、自分のリポジトリとブランチを指定します。
保存をクリックするとデプロイが開始されGitHubリポジトリのActionsタブで確認することができます。
4. GitHub Actionsで定期実行する
スクレイピングで取得した最新のデータをWebサイトに反映させる必要があるので、GitHub Actionsを用いてスクレイピングをスケージュールトリガーで実行します。
GitHub Actionsのワークフローを定義するyamlファイルは、リポジトリのルートディレクトリに.github
フォルダを作成し、その中にworkflows
というフォルダを作成し、その中にyaml
ファイルを作成します。
on:
以降にアクショントリガーを設定します。
on:
workflow_dispatch:
schedule:
# JST: 0, 6, 12, 18 -> UTC: 15, 21, 3, 9
- cron: "0 15 * * *"
- cron: "0 21 * * *"
- cron: "0 3 * * *"
- cron: "0 9 * * *"
workflow_dispatch
は手動でワークフローを起動するためのトリガーです。テストするのに便利なので設定しておきます。
schedule
はcron
式を使用して定期的な実行のタイミングを指定します。
Cron式は以下のような構造になっています
* | * | * | * | * |
---|---|---|---|---|
分 | 時 | 日 | 月 | 曜日 |
0 - 59 | 0 - 23(UTC時間) | 1 - 31 | 1 - 12 | 0 - 7(0,7が日曜) |
注意したいのが、時間はUTC時間であることです。UTC(協定世界時)はUTC = JST - 9時間
で表現されます。現在はUTCに置き換わりつつありますが、GMTと同じ時間を示します。ここでは、毎日0,6,12,18時に更新したいので、"0 15,21,3,9 * * *”
と指定しています。
最後にGitHub Actionsでの書き込み権限を設定します。Settings
タブからActions
のGeneral
を選択します。Workflow permissions
をRead and write permissions
に変更することで書き込み権限を与えられます。
5. Notionに埋め込む
デプロイが完了し、Webサイトが問題なく表示されたらNotionに埋め込みます。
サイトのURLをNotionに張り付けて「埋め込み」を選択すれば完成です。
おわりに
GitHub Actionsで更新しているのでコントリビューションが汚染されてしまうことに気づき現在は自動更新を停止しています。解決策として、別のユーザでコミットを行う方法を検討中です。ここまで読んでいただきありがとうございました。
参考にしたサイト等