Skip to content
  • Hash icon  Golang
  • Hash icon  GitHub
  • Hash icon  個人開発
  • Go言語でGitHub Bot作ってみた

    Calendar icon Published:
    Go言語でGitHub Bot作ってみた

    「GitHubでIssue作ったときに自動でコメントしてくれるBotあったらいいな〜」

    そんな軽い気持ちで始めたGitHub Bot作り。最初は面倒そうだと思ってましたが、実際やってみたら意外とサクッとできました。

    今回はGo言語でGitHub App(Bot)を個人開発した体験談として、実際にハマったポイントや「ここはこうした方がよかった」という話を共有します。

    🎯 作ったもの

    Issueが作成されると、こんな感じで自動コメントしてくれるBot:

    🌸 @username さん、お疲れさまです!新しいIssueを作成していただき、ありがとうございます。一歩一歩、着実に進んでいきましょう ✨

    シンプルですが、なんか温かみがあって気に入ってます。

    🛠️ 使った技術スタック

    • Go言語 - シンプルで書きやすい
    • GitHub App - 公式の推奨方法
    • ngrok - ローカル開発で外部公開
    • go-github - GitHub API クライアント

    📝 実際の開発手順

    1. GitHub App作成(一番重要)

    GitHub Settings から「New GitHub App」で作成。

    設定で重要だったポイント:

    GitHub App name: my-simple-bot
    Webhook URL: 一旦 https://example.com/webhook (後で変更)
    Webhook secret: mysecret123
    Repository permissions:
      - Issues: Read & write
    Subscribe to events:
      - Issues ✅

    ここでハマった: 最初Webhook URLを空にしてActiveのチェックを外していたのですが、後でngrokのURL設定するとき手間でした。最初から仮のURLでも入れておくべきでした。

    2. 必要な情報をメモ

    • App ID: 作成後の画面に表示される
    • Private key: 「Generate a private key」でダウンロード
    • Webhook secret: 上で設定したやつ

    この3つは後で環境変数で使います。

    3. Go環境セットアップ

    mkdir simple-github-bot
    cd simple-github-bot
    go mod init simple-github-bot
    go get github.com/google/go-github/v29/github
    go get github.com/bradleyfalzon/ghinstallation

    4. コード実装

    メインのコードはこんな感じです:

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    	"strconv"
    	"time"
    
    	"github.com/bradleyfalzon/ghinstallation"
    	"github.com/google/go-github/v29/github"
    )
    
    func main() {
    	http.HandleFunc("/webhook", handleWebhook)
    	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		fmt.Fprintln(w, "🤖 Bot is running!")
    	})
    
    	log.Println("🚀 Bot starting on port 8080...")
    	log.Fatal(http.ListenAndServe(":8080", nil))
    }
    
    func handleWebhook(w http.ResponseWriter, r *http.Request) {
    	secret := os.Getenv("WEBHOOK_SECRET")
    	payload, err := github.ValidatePayload(r, []byte(secret))
    	if err != nil {
    		log.Printf("❌ Invalid payload: %v", err)
    		http.Error(w, "Invalid payload", http.StatusBadRequest)
    		return
    	}
    
    	event, err := github.ParseWebHook(github.WebHookType(r), payload)
    	if err != nil {
    		log.Printf("❌ Parse error: %v", err)
    		http.Error(w, "Parse error", http.StatusBadRequest)
    		return
    	}
    
    	if issueEvent, ok := event.(*github.IssuesEvent); ok {
    		if issueEvent.GetAction() == "opened" {
    			handleIssueEvent(r.Context(), issueEvent)
    		}
    	}
    
    	w.WriteHeader(http.StatusOK)
    }
    
    func handleIssueEvent(ctx context.Context, event *github.IssuesEvent) {
    	log.Printf("📝 New issue: %s", event.GetIssue().GetTitle())
    
    	client, err := createGitHubClient(event.GetInstallation().GetID())
    	if err != nil {
    		log.Printf("❌ Failed to create client: %v", err)
    		return
    	}
    
    	user := event.GetIssue().GetUser().GetLogin()
    	comment := fmt.Sprintf(
    		"🌸 @%s さん、お疲れさまです!新しいIssueを作成していただき、ありがとうございます。一歩一歩、着実に進んでいきましょう ✨",
    		user,
    	)
    
    	issueComment := &github.IssueComment{Body: &comment}
    
    	_, _, err = client.Issues.CreateComment(
    		ctx,
    		event.GetRepo().GetOwner().GetLogin(),
    		event.GetRepo().GetName(),
    		event.GetIssue().GetNumber(),
    		issueComment,
    	)
    
    	if err != nil {
    		log.Printf("❌ Failed to post comment: %v", err)
    		return
    	}
    
    	log.Printf("✅ Comment posted successfully!")
    }
    
    func createGitHubClient(installationID int64) (*github.Client, error) {
    	appID, err := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
    	if err != nil {
    		return nil, err
    	}
    
    	transport, err := ghinstallation.NewKeyFromFile(
    		http.DefaultTransport,
    		appID,
    		installationID,
    		"private-key.pem",
    	)
    	if err != nil {
    		return nil, err
    	}
    
    	return github.NewClient(&http.Client{
    		Transport: transport,
    		Timeout:   10 * time.Second,
    	}), nil
    }

    5. ローカルテスト

    環境変数設定:

    export GITHUB_APP_ID=123456
    export WEBHOOK_SECRET=mysecret123

    ngrokで外部公開:

    # 別ターミナルで
    ngrok http 8080

    GitHub AppのWebhook URL更新: ngrokで表示されたhttps URLを使って https://abc123.ngrok.io/webhook に変更。

    Botを起動:

    go run main.go

    6. 動作確認

    GitHub AppをリポジトリにインストールしてIssueを作成。うまくいけばBotがコメントしてくれます!

    😅 実際にハマったポイント

    1. 秘密鍵のファイル名

    ダウンロードした秘密鍵ファイルが app-name.2025-05-27.private-key.pem みたいな長い名前だったので、private-key.pem にリネームするのを忘れてました。

    2. Installation IDの取得

    最初はInstallation IDをハードコードしてましたが、Webhookのペイロードから取得する方が正しいです。複数のリポジトリにインストールする可能性もありますしね。

    3. Webhook検証を後回しにした

    最初は検証なしで作ってましたが、セキュリティ的によくないので早めに実装すべきでした。github.ValidatePayloadで簡単にできます。

    💡 改善案・拡張アイデア

    今回作ったのはシンプルなBotでしたが、以下のような拡張も面白そうです:

    • AI連携: ChatGPT APIと連携してより賢いコメント
    • ラベル自動付与: Issue内容を解析して適切なラベルを自動追加
    • プルリクエスト対応: PRにも自動レビューコメント
    • Slack通知: 重要なIssueをSlackにも通知

    🎉 まとめ

    Go言語でGitHub Bot作るのは思ったより簡単でした!特に:

    • GitHub App: 公式の推奨方法で安全
    • go-github: ライブラリが充実していて使いやすい
    • ngrok: ローカル開発が楽になる

    30分〜1時間もあれば基本的なBotは作れるので、何か自動化したいことがあったらぜひ試してみてください。

    個人開発でも十分活用できますし、チーム開発なら更に威力を発揮しそうです。

    参考リンク: