GithubのContribution数を定時に報告してくれるDiscord botを作りたい
技術選び
Google App Script (GAS)
- トリガーを設定して毎日決められた時間にGASを実行できる
- 無料でデプロイできそう
Github REST API or GraphQL API
- RESTもあったがとってこれるデータが少なそう
- GraphQLでもやってみる
Discord側の設定
Discord側でテキストチャンネルの編集 > 連携サービス > 新しいウェブフックで作成する
謎のWebhookが出来るので適当に編集
ウェブフックURLをコピーからWebhookのURLを取得する.このURLにリクエストボディを{content: "message"}
としてPOSTすれば良さそう
username
とかavatar_url
を設定すればbotの名前とアバターを上書きできるらしい
GASのコーディング
GoogleドライブでGoogle App Script
を作成して開く
Github REST APIを叩いてみる
const USERNAME = "<GithubのアカウントID>"
const DISCORD_URL = "<WebhookのURL>"
const GITHUB_URL = "https://api.github.com/users/" + USERNAME + "/events"
// Github APIからContribution数を取得する
function getNumOfContributions (date) {
// リクエストのオプション
let options = {
"method": "GET",
"muteHttpExceptions": true
}
// Github APIからデータを取得する
let response = UrlFetchApp.fetch(GITHUB_URL, options)
// Contribution数を取得する
if (response.getResponseCode() === 200) {
// 正しくレスポンスが返ってきた場合
// レスポンスをパース
let events = JSON.parse(response.getContentText())
// Contribution数を計算する
// Contributionとしてカウントするイベントの種類 https://docs.github.com/ja/rest/using-the-rest-api/github-event-types?apiVersion=2022-11-28
let contributions = events.filter(function(event) {
return (event.type === "PushEvent") && event.created_at.startsWith(date)
}).length
return contributions
} else {
// レスポンスが返ってこなかった場合,エラーを投げる
throw new Error("Github APIにアクセスできませんでした.")
}
}
// DiscordのWebhookにメッセージを登録
function postMessage (message) {
// 登録するメッセージ
let payload = {
"content": message
}
// リクエストのオプション
let options = {
"method": "post",
"payload": payload
}
// Webhookにリクエストを投げる
UrlFetchApp.fetch(DISCORD_URL, options)
}
// エントリポイント
function main () {
try {
// 今日のContribution数を取得してメッセージを送信する
let today = new Date().toISOString().slice(0, 10) // 2000-01-01のような形式の日付
let contributions = getNumOfContributions(today)
postMessage(`【${today}のContribution数】 ${contributions}`)
} catch (e) {
// エラーが生じた場合,その内容を送信する
console.error(e)
postMessage(`【エラーが発生しました】 ${e}`)
}
}
main
関数を実行
ちゃんと送信できてるっぽい
上手くいってないっぽい
計算したContribution数と実際のContribution数が一致していない
Contribution数はレスポンスからevent.type
がPushEvent
のものを数えていたが,ドキュメントをよく読んでみたらPushEvent
はブランチに1つ以上のコミットがプッシュされたときに発生するイベントらしい.
これでは3つのコミットを1度にプッシュした場合でも1 Contributionとして扱われてしまう.あと,そもそもPRとかIssueで発生するContributionも数えられていない
Github GraphQL APIを使ってみる
Github CLIからGraphQL APIを試せるらしいのでやってみる.取得するデータはこれ
gh api graphql -f query='
query contributions {
user(login: "tf63") {
contributionsCollection(to: "2024-04-10T00:00:00", from: "2024-04-09T00:00:00") {
contributionCalendar {
weeks {
contributionDays {
date
contributionCount
}
}
}
}
}
}
'
レスポンス
(略)
"contributionDays": [
{
"date": "2024-04-09",
"contributionCount": 2
},
{
"date": "2024-04-10",
"contributionCount": 1
}
]
(略)
ちゃんとContribution数を取得できてそう
GASからGithub GraphQL APIを叩く
Github GraphQL APIの場合はアクセストークンが必ず必要? みたいなので取得します
Githubの Settings > Developer Settings > Tokens (classic) から Generate new token > Generate new token (classic) を選択します
Expiration (トークンの有効期限) を適当に設定し,user > read:user の項目のみチェックを入れてトークンを生成します
ghp_<...>
みたいな文字列がトークンです.絶対に公開しないでください
これを使ってGASのコーディングをします
(一部修正しました)
const USERNAME = "<GithubのアカウントID>"
const GITHUB_TOKEN = "<作成したトークン>"
const DISCORD_URL = "<WebhookのURL>"
const GITHUB_URL = "https://api.github.com/graphql" // 変更
// UTCだったのでJSTに合わせる.良い方法無いですかね
let todayUTC = new Date()
todayUTC.setHours(todayUTC.getHours() + 9)
const TODAY = todayUTC.toISOString() // 2000-01-01T00:00:00.000Zのような形式の日付
// GraphQLのクエリ
// 今日のContribution数を取得する
const query = `query contributions {
user(login: "${USERNAME}") {
contributionsCollection(to: "${TODAY}", from: "${TODAY}") {
contributionCalendar {
weeks {
contributionDays {
date
contributionCount
}
}
}
}
}
}
`
// Github APIからContribution数を取得する
function getNumOfContributions () {
// リクエストのオプション (GraphQLって常にGETだったっけ?)
let options = {
"method": "GET",
"headers": {
"Authorization": `Bearer ${GITHUB_TOKEN}`,
"Content-Type": "application/json"
},
"payload": JSON.stringify({ query })
}
// Github APIからデータを取得する
let response = UrlFetchApp.fetch(GITHUB_URL, options)
// Contribution数を取得する
if (response.getResponseCode() === 200) {
// 正しくレスポンスが返ってきた場合
// レスポンスをパース
let datas = JSON.parse(response.getContentText())
// 適当にcontribution数を取り出す
let contribution = datas.data.user.contributionsCollection.contributionCalendar.weeks[0].contributionDays[0].contributionCount
return contribution
} else {
// レスポンスが返ってこなかった場合,エラーを投げる
throw new Error("Github APIにアクセスできませんでした.")
}
}
// DiscordのWebhookにメッセージを登録
function postMessage (message) {
// 登録するメッセージ
let payload = {
"content": message
}
// リクエストのオプション
let options = {
"method": "post",
"payload": payload
}
// Webhookにリクエストを投げる
UrlFetchApp.fetch(DISCORD_URL, options)
}
// エントリポイント
function main () {
try {
// 今日のContribution数を取得してメッセージを送信する
let contribution = getNumOfContributions()
postMessage(`【${TODAY.slice(0, 10)}のContribution数】 ${contribution}`)
} catch (e) {
// エラーが生じた場合,その内容を送信する
console.error(e)
postMessage(`【エラーが発生しました】 ${e}`)
}
}
動作確認
トリガーを設定
最後に作成したmain
関数を定時実行するように設定します
GASのトリガータブから トリガーを追加 を選択します
こんな感じで出来るのではないでしょうか
今日の22時が楽しみですね