はじめに
Naraと申します。DMMWEBCAMPで勉強後、2022年1月より都内の受託会社で勤務を始めました。
この記事では、
pythonをベースに、APIでgithubのcommit数の算出と、twitterの自動投稿を行い、github Actionsでそれらを自動化する方法をお伝えします。
(ただし、pythonの知識が少ないことに加え、勢いで書いているので煩雑なコードになっていることをお許しください。)
日々githubでcommitを重ねて頑張っているあなた。
Twitterにcommit数を記録していきませんか?
また、その努力をフォロワーに共有して成果をアピールしませんか?
この記事の対象
こちらの記事は詳細編ということで、以前投稿した簡易編を掘り下げてコードの解説を行います。
1からBotを作成してみたい。APIをどのようにして使用しているのか知りたいといった方が対象です。
1からの作成は嫌ですという方へ
私が作成した上記リポジトリをforksしてください。(starいただけると喜びます。)
また、導入の仕方は簡易編と題して投稿したQiita を参考ください。
作成手順
以下作成手順です。
- 環境構築
- Github APIによるcommit数の集計
- Twitterへの投稿
- Github Actionsの導入
- GitHub Actionsの実行
Twitter Botの作成前に
TwitterBotの作成には、事前に以下の情報が必要です。
- APIKey
- APIKeySercret
- Access Token
- Access Token Secret
取得の方法は別途記事にまとめましたので、ご確認ください。
※記事は2022/04現在のものです。ここ数年で仕様が何回か変化しているので、ご注意ください。
GitHub APIの使用について
GitHub APIを使用する場合、同一IPからのアクセスは一時間に60回までの制限があります。
今回のCommit数の集計は何回もGitHubAPIを取得する必要がありますので、何も考えずに実装を行うと上記の制限に引っかかる可能性が極めて高いです。
こちらの制限はOAuthにより
- Client ID
- Client Secret
を取得することで、5000 requests/hourまでAPIでのアクセスが緩和可能となります。
こちらも取得をお願いします。
取得の方法は別途記事としてまとめてありますので、参照してください。
1. 環境構築
python自体のインストールは割愛します。
私の実行環境は以下です。(一応pyenvで管理しています。記事には関係ありません。)
$ python --version
Python 3.8.12
$ pyenv --version
pyenv 2.2.3
$ pip --version
pip 22.0.4 from /Users/user/.pyenv/versions/3.8.12/lib/python3.8/site-packages/pip (python 3.8)
パッケージインストール
$ pip install pytz # time-zoneを扱うライブラリ
$ pip install tweepy # twitterのAPIを操作可能なライブラリ
$ pip install python-dotenv # 環境変数の取り扱いを行うライブラリ
GithubAPIから取得する情報はUTC(協定世界時)ですが、GithubActionsで取得されるtime情報はアカウントに依存するため、pytzで時間を合わせる必要があります。
tweepyはtwitterのAPIを操作可能なライブラリで、これを使用してbotツイートします。
利用には先に述べたAPIkeyやAccessTokenが必要です。
dotenvは環境変数を取り扱うライブラリで、上記のAPIKey等々を格納します。
(私のリポジトリをクローンしてある場合は、格納サンプルとして.env.sampleというファイルだけが存在します。)
環境変数の設定
$ mkdir git-commit-bot # フォルダ作成
$ cd git-commit-bot # フォルダ配下に移動
$ touch .env # .envファイル作成
TWITTER_CONSUMER_API_KEY = "<API key>"
TWITTER_CONSUMER_API_SECRET_KEY = "<API Key Secret>"
TWITTER_ACCESS_TOKEN = "<Access Token>"
TWITTER_ACCESS_TOKEN_SECRET = "<Access Token Secret>"
GIT_USERNAME = "<UserName>"
GIT_CLIENT_ID = "<Client ID>"
GIT_CLIENT_SECRETS = "<Client secrets>"
フォルダ配下に移動し、.envを作成します。
.envには「Twitter Bot作成の前に」、「Github APIの使用について」でそれぞれお話しした、API keyやTokenの情報が入ります。適宜設定してください。
この.envファイルはローカル環境での動作確認の際に使用します。
私の公開リポジトリをDLした方は、.env.sampleという形でサンプルファイルがありますので、こちらを.envに直してお使いください。
メインファイル/設定用ファイルの作成
$ touch main.py # メインファイル
$ touch settings.py # 環境変数変換用
# 必要ライブラリのインポート
import requests,json
import datetime
import pytz
import tweepy
import settings
import os
from os.path import join, dirname
from dotenv import load_dotenv
# 環境変数のセッティング
dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)
TWITTER_CONSUMER_API_KEY = os.environ.get("TWITTER_CONSUMER_API_KEY")
TWITTER_CONSUMER_API_SECRET_KEY = os.environ.get("TWITTER_CONSUMER_API_SECRET_KEY")
TWITTER_ACCESS_TOKEN = os.environ.get("TWITTER_ACCESS_TOKEN")
TWITTER_ACCESS_TOKEN_SECRET = os.environ.get("TWITTER_ACCESS_TOKEN_SECRET")
GIT_USERNAME = os.environ.get("GIT_USERNAME")
GIT_CLIENT_ID = os.environ.get("GIT_CLIENT_ID")
GIT_CLIENT_SECRETS = os.environ.get("GIT_CLIENT_SECRETS")
今回はメインファイル(main.py)と環境変数変換用ファイル(settings.py)の2種類を作成しています。
メインファイルは先ほどpipでインストールしたpytzやtweepyを含めた必要ライブラリをインポートしてください。
また、環境変数変換用ファイルにはdotenvライブラリをインストールし、.envから環境変数をインポートしてください。
以上で環境構築は終了です。
2. Github API commit数を集計
以下main.pyへの追記となります。
import requests,json
import datetime
import pytz
import tweepy
import settings
# TimeZone/日付 指定
jp = pytz.timezone('Asia/Tokyo')
finish_date = datetime.datetime.now().astimezone(jp).replace(hour=0,minute=0,second=0,microsecond=0) # 集計終了日(= Today )
start_date = finish_date - datetime.timedelta(1) # 集計開始日(= 1日前)
# Git の設定
git_username = settings.GIT_USERNAME
git_client_id = settings.GIT_CLIENT_ID
git_client_secrets = settings.GIT_CLIENT_SECRETS
# query設定
git_client_id_query = "&client_id=" + git_client_id
git_client_secrets_query = "&client_secret=" + git_client_secrets
# Twitter APIのキーを設定
consumer_key = settings.TWITTER_CONSUMER_API_KEY
consumer_secret = settings.TWITTER_CONSUMER_API_SECRET_KEY
# Twitter APIのアクセストークンを設定
access_token=settings.TWITTER_ACCESS_TOKEN
access_token_secret =settings.TWITTER_ACCESS_TOKEN_SECRET
# 変数初期化
total_commit_count = 0 # 合計commit数
repo_url_page_num = 1 # repository取得の際のpagenation用
# Gitの情報を取得
while True:
# レポジトリを取得(1ページ最大100件)
repositories_url = "https://api.github.com/users/{0}/repos?per_page=100&page={1}{2}{3}".format(git_username, repo_url_page_num, git_client_id_query, git_client_secrets_query)
git_repositories = requests.get(repositories_url).text
repositories = json.loads(git_repositories)
if git_repositories == "[]":
break
elif type(repositories) != list and "API rate limit exceeded" in repositories["message"] :
total_commit_count = "Sorry, Today's Github API is limited..."
break
else:
pass
for repository in repositories:
# repository の名前/pushed_at(push日)を取得
repo_name = repository["name"]
repo_update_date = datetime.datetime.fromisoformat(repository["pushed_at"].replace('Z', '+00:00')).astimezone(jp)
commit_url_page_num = 1
# updateが集計開始日より後であればcommit情報を取得
if start_date < repo_update_date:
while True:
## 各リポジトリのcommitを取得
git_commits_url = "https://api.github.com/repos/{0}/{1}/commits?per_page=100&page={2}{3}{4}".format(git_username, repo_name, commit_url_page_num, git_client_id_query, git_client_secrets_query)
git_commits = requests.get(git_commits_url).text
commits = json.loads(git_commits)
# API 取得がlimitを超えてしまった場合の例外処理
if git_commits == "[]":
break
elif type(commits) != list and "API rate limit exceeded" in commits["message"] :
total_commit_count = "Sorry, Today's Github API is limited..."
break
else:
pass
# commitの内容を取得
for commit in commits:
# commit の日付を取得
commit_datetime = datetime.datetime.fromisoformat(commit["commit"]["committer"]["date"].replace('Z', '+00:00')).astimezone(jp)
# commitの日付によって処理を振り分け
if start_date < commit_datetime:
if commit_datetime < finish_date:
# commitの日付が集計開始日より後なら1カウント
total_commit_count += 1
else:
# commitの日付が開始日よりも前ならbreakして次のリポジトリへ
break
commit_url_page_num += 1 # paginationを進める
repo_url_page_num += 1 # paginationを進める
以下で解説します。
time-zone/集計期間の設定
jp = pytz.timezone('Asia/Tokyo') # time-zone設定
finish_date = datetime.datetime.now().astimezone(jp).replace(hour=0,minute=0,second=0,microsecond=0) # 集計終了日(= Today)
start_date = finish_date - datetime.timedelta(1) # 集計開始日(= 1日前)
GithubAPIで得られるdatetimeはUTC(協定世界時)で返されますが、後述するGitHubActionsでは所属リージョンによってdatetimeが設定されているため、pytzであらかじめtime-zoneの設定をしておく必要があります。
また、集計期間も一緒に設定してあげます。
私は昨日1日の集計が欲しかったため、start/finishを上記の設定としていますが、集計に必要な期間を逆算して各自設定してください。
ただし、GitHubAPIは上限解放しても5000 requests/hourまでとなりますので、集計期間が長いとlimitに引っかかる恐れがあります。ご注意を。
各種keyの再設定
# Git の設定
git_username = settings.GIT_USERNAME
git_client_id = settings.GIT_CLIENT_ID
git_client_secrets = settings.GIT_CLIENT_SECRETS
# Twitter APIのキーを設定
consumer_key = settings.TWITTER_CONSUMER_API_KEY
consumer_secret = settings.TWITTER_CONSUMER_API_SECRET_KEY
# Twitter APIのアクセストークンを設定
access_token=settings.TWITTER_ACCESS_TOKEN
access_token_secret =settings.TWITTER_ACCESS_TOKEN_SECRET
settingsに記載の定数を毎回settingsを頭にして呼び出すのは面倒なので、設定し直します。
queryの設定(オプション)
# query設定
git_client_id_query = "&client_id=" + git_client_id
git_client_secrets_query = "&client_secret=" + git_client_secrets
GitHubAPIの上限緩和の際に用いるClientIDやClientSecretsは主にqueryとして使用します。
都度文字列として結合しても良いのですが、見栄えが悪くなるので、私は1つにまとめました。
Gitの情報を取得する
# 変数初期化
total_commit_count = 0 # 合計commit数
repo_url_page_num = 1 # repository取得の際のpagenation用
while True:
# レポジトリを取得(1ページ最大100件)
repositories_url = "https://api.github.com/users/{0}/repos?per_page=100&page={1}{2}{3}".format(git_username, repo_url_page_num, git_client_id_query, git_client_secrets_query)
git_repositories = requests.get(repositories_url).text
repositories = json.loads(git_repositories)
(後述の処理:中略)
repo_url_page_num += 1 # paginationを進める
URL = "https://api.github.com/users/"<UserName>"/repos"
変数初期化として合計commit数を計算する「total_commit_count」と後述するpaginationに対応するための「repo_url_page_num」を定義しています。
次にGitHubAPIでリポジトリの情報を取得します。Repositoryを取得する GitHubAPIは別途にも示した通りです。
APIのデフォルトでは1度に取得できる情報が30個のリポジトリとなっており、自動的にpagenationが組まれている点に注意が必要です。
1回でより多くの情報を取得し、pagenationに対応するためには、クエリに「per_page」と「page」の表記を付け加えます。
per_pageは1度に取得する情報数のことで、最大100件までです。
pageはpagenationの番号で、1から開始します。
repositoryのURLを指定しrequests.getで情報を取得したのち、一度textに変換します。
その後、jsonとして認識させるため、json.loadします。
後述するリポジトリ取得後の処理を行ない、処理が完了したらpagenationを進めてwhile処理で回していきます。
while文は常にTrueで回しAPIの取得が終了した場合にbreakされます。
while文をbreakするタイミング
# API取得終了または取得制限に引っかかった場合の例外処理
if git_repositories == "[]":
break
elif type(repositories) != list and "API rate limit exceeded" in repositories["message"] :
total_commit_count = "Sorry, Today's Github API is limited..."
break
else:
pass
while文をbreakするタイミングは、取得したtext情報がリストの形で何も入っていないとき、つまり全てのリポジトリ情報の取得が終了したときに設定してあります。
また、先ほどから述べている通りGitHubAPIには取得制限が存在します。(上限緩和後5000 requests/hour)
この制限に引っかかった時にwhile文を強制的に抜ける必要があるので、elseでこちらもbreak処理を行なっています。
各リポジトリからリポジトリ名とpush日を取得する
for repository in repositories:
# repository の名前/pushed_at(push日)を取得
repo_name = repository["name"]
repo_pushed_date = datetime.datetime.fromisoformat(repository["pushed_at"].replace('Z', '+00:00')).astimezone(jp)
APIで取得したリポジトリ(最大100件)から各リポジトリ名「repo_name」とpush日「repo_pushed_date」の取得を行います。
push日の取得はGitHubAPIの制限を考えて、集計開始日より後にpushしていないリポジトリを事前に集計対象から外すためです。
また、push日はdatetimeオブジェクトになりますので、ここでもタイムゾーンの指定をしておきます。
commit情報の取得
URL = "https://api.github.com/repos/"<UserName>"/"<Repository Name>"/commits"
# pushed_dateが集計開始日より後であればcommit情報を取得
if start_date < repo_pushed_date:
## 各リポジトリのcommitを取得
git_commits_url = "https://api.github.com/repos/{0}/{1}/commits?per_page=100&page={2}{3}{4}".format(git_username, repo_name, commit_url_page_num, git_client_id_query, git_client_secrets_query)
git_commits = requests.get(git_commits_url).text
commits = json.loads(git_commits)
repo_pushed_dateが集計開始日よりも後であればcommitの情報を取得します。取得の仕方はrepositoryの場合と同様です。
commitを取得する際のAPIも別途示しました。
commit日付を取得して集計
for commit in commits:
# commit の日付を取得
commit_datetime = datetime.datetime.fromisoformat(commit["commit"]["committer"]["date"].replace('Z', '+00:00')).astimezone(jp)
# commitの日付によって処理を振り分け
if start_date < commit_datetime:
if commit_datetime < finish_date:
# commitの日付が集計開始日より後なら1カウント
total_commit_count += 1
else:
# commitの日付が開始日よりも前ならbreakして次のリポジトリへ
break
APIで取得したcommitの全数情報から、各々のcommitの日付をfor文で取得します。
ここでも日付はtime-zoneを設定しておき、この取得した日付によって処理を振り分けます。
commitの日付が集計開始日より後ならtotal_commit_countに1カウントして次のcommit日付を調べます。
commitの日付が開始日よりも前ならfor文をbreakして次のリポジトリを調べます。
長かったですが、こちらでGitHubの情報の集計は終了です。
3.Twitterへ投稿
main.pyに下記を追記します。
# Twitterへの投稿
## TwitterClientの作成
client = tweepy.Client(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token=access_token,
access_token_secret=access_token_secret
)
## 投稿文
raw_text = '''【Github Commit Bot】
Github Name: {0}
Github URL: https://github.com/{1}
Period : {2} - {3}
Total Commit : {4}commit
#GitHub #GitHubCommitBot'''
text = raw_text.format(git_username, git_username, start_date.strftime('%m/%d'), finish_date.strftime('%m/%d') ,total_commit_count)
## 投稿
if type(total_commit_count) == int:
client.create_tweet(text=text)
elif type(total_commit_count) == str:
client.create_tweet(text=total_commit_count)
else:
pass
以下で解説します。
Twitter Clientの作成
client = tweepy.Client(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token=access_token,
access_token_secret=access_token_secret
)
Tweepyライブラリを用いて、所望のTwitterアカウントの情報からclientオブジェクトを設定します。
投稿文の設定
raw_text = '''【Github Commit Bot】
Github Name: {0}
Github URL: https://github.com/{1}
Period : {2} - {3}
Total Commit : {4}commit
#GitHub #GitHubCommitBot'''
text = raw_text.format(git_username, git_username, start_date.strftime('%m/%d'), finish_date.strftime('%m/%d') ,total_commit_count)
投稿文を作成します。Twitterの文字数制限に引っかからないように注意してください。
ツイートの投稿
if type(total_commit_count) == int:
client.create_tweet(text=text)
elif type(total_commit_count) == str:
client.create_tweet(text=total_commit_count)
else:
pass
ツイートはclientオブジェクトにcreate_tweetメソッドで投稿したい文章を渡してあげると投稿が可能です。
ここではtotal_commit_countが整数値出会った場合にそのままtextを出力します。
もしAPIの制限に引っかかっている場合は、事前にtotal_commit_countに「Sorry, Today's Github API is limited...」とstrを入れていますので、こちらを投稿することになります。
以上でtwitterへの投稿機能の実装は終了です。
ここまでで、正しく実行可能か確認するためには
$ python main.py
で確認可能です。
4. GitHub Actionsの導入
上記1~3で作成したフォルダをGitHubのリポジトリにアップロードします。
repositoryやorigin、branchの設定は各自行なっているものとします。
$ cd git-commit-bot # フォルダ配下に移動
$ git add -A
$ git commit -m "○○" # 任意のコメント
$ git push origin main
自分のリポジトリにpushできたら、GitHub上ででActionsの操作をしていきます。
Actionsについて
GitHubActionsは私も初心者なので、詳細は上記の記事を参考にすると良いかもしれません。
Jobが実行される仮想環境のスペックは次の通り。
そんなにスペックは高くない。
2コアCPU
7 GBのRAMメモリー
14 GBのSSDディスク容量
Publicリポジトリなら無料。
Privateだと Linux仮想環境で $0.008/min かかる。
0.008/min=0.008/min=0.48/hour = 約52円/hour($1=108.4円)
10分かかるビルドを実行すると約9円かかる。
Freeアカウントで2,000分/月 無料。
Botの投稿には1分ほどしかかからないので、無料枠で収まる範囲です。
環境変数の登録
まず、main.pyを実行するためには環境変数をリポジトリに登録する必要があります。
作成したリポジトリの「Settings」をクリックします。
左のNavigationからSercrets-Actionsをクリックします。
New repository secretを選択します。
この画面で環境変数を設定します。
登録が必要な変数名は以下になります。
- TWITTER_CONSUMER_API_KEY = "API key"
- TWITTER_CONSUMER_API_SECRET_KEY = "API Key Secret"
- TWITTER_ACCESS_TOKEN = "Access Token"
- TWITTER_ACCESS_TOKEN_SECRET = "Access Token Secret"
- GIT_USERNAME = "UserName"
- GIT_CLIENT_ID = "Client ID"
- GIT_CLIENT_SECRETS = "Client secrets"
7種類全てをこの変数名で登録してください。
登録が完了すると、Repository secretsに上記のような形で登録が確認できます。
リポジトリに環境変数を付与できました。
Actionsの登録
Botを定期実行するActions用のファイルを作成していきます。
リポジトリのActionsをクリックします。
set up a workflow yourselfをクリックします。
上記のような画面に遷移します。
この画面で定期実行用のmain.ymlを作成します。
name: git_commit_bot
on:
workflow_dispatch:
schedule:
- cron: '0 15 * * *'
jobs:
build:
# Ubuntuの最新版環境内で処理を実行することを指定
runs-on: ubuntu-latest
# 実行する処理&コマンド指定
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytz
pip install tweepy
pip install python-dotenv
touch .env
echo "TWITTER_CONSUMER_API_KEY=${{ secrets.TWITTER_CONSUMER_API_KEY }}" >> .env
echo "TWITTER_CONSUMER_API_SECRET_KEY=${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}" >> .env
echo "TWITTER_ACCESS_TOKEN=${{ secrets.TWITTER_ACCESS_TOKEN }}" >> .env
echo "TWITTER_ACCESS_TOKEN_SECRET=${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}" >> .env
echo "GIT_USERNAME=${{ secrets.GIT_USERNAME }}" >> .env
echo "GIT_CLIENT_ID=${{ secrets.GIT_CLIENT_ID }}" >> .env
echo "GIT_CLIENT_SECRETS=${{ secrets.GIT_CLIENT_SECRETS }}" >> .env
- name: Run script
run: |
python main.py
以下素人ながら少しだけ解説します。
実行処理
on:
workflow_dispatch: #手動実行用
schedule: # 定期実行用
- cron: '0 15 * * *'
workflow_dispatchはGitHubActionsから手動でmain.ymlを実行可能にしてくれます。
またScheduleに記載したcronではUTC-15:00つまり、日本時間00:00に処理を開始することを意味しています。
このcronの部分は1時間毎や1日毎といった処理にも変更可能です。詳しくは参考サイトを確認ください。
python install
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
Actions仮想環境にpython3.8をDLしています。
pipの更新とライブラリの導入
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytz
pip install tweepy
pip install python-dotenv
pipをインストールして更新し、必要なライブラリを導入しています。
.envの作成と環境変数の登録
touch .env
echo "TWITTER_CONSUMER_API_KEY=${{ secrets.TWITTER_CONSUMER_API_KEY }}" >> .env
echo "TWITTER_CONSUMER_API_SECRET_KEY=${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}" >> .env
echo "TWITTER_ACCESS_TOKEN=${{ secrets.TWITTER_ACCESS_TOKEN }}" >> .env
echo "TWITTER_ACCESS_TOKEN_SECRET=${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}" >> .env
echo "GIT_USERNAME=${{ secrets.GIT_USERNAME }}" >> .env
echo "GIT_CLIENT_ID=${{ secrets.GIT_CLIENT_ID }}" >> .env
echo "GIT_CLIENT_SECRETS=${{ secrets.GIT_CLIENT_SECRETS }}" >> .env
touch .envで.envを作成します。
また、.envにechoで環境変数を登録しています。リポジトリに登録した環境変数は"secrets.○○"の形で呼び出すことができます。
実行
- name: Run script
run: |
python main.py
最後にmain.pyを実行します。
main.pyにはsetting.py等のファイルをインポートしていましたが、リポジトリに存在するファイルは使用可能ですのでpythonコマンドでmain.pyを実行するだけです。
5. GitHub Actionsの実行
最後に、手動でActionsを実行してみます。
リポジトリのActionsからWorkflowsを確認し、先ほど登録したワークフローを選択します。
main.ymlに正しくworkflow_dispatchが記述されていれば上記の内容が表示されるので、Run workflowをクリックします。
実行して良いのであればRun workflowをクリックします。
上記の画面に遷移し、実行が成功すると緑のチェックマークがつきます。
ログは中央の"build"をクリックすると参照可能です。エラーの場合ヒントになりますので、確認してみてください。
Twitterを確認して、投稿を確認してみてください。
終わりに
GitHubActionsを使用して、Twitterに定期ツイートをするBotを作成してみました。
今回はcommit数に限定しましたが、GitHubのAPIで取得可能な情報は多々あるので自分の知りたい情報を取得すればより幅が広がるかなと思っています。
バグ等の情報は随時連絡いただければと思います。よろしくお願いします。
参考サイト