mirror of
https://github.com/taigrr/github-to-signal.git
synced 2026-04-17 10:54:44 -07:00
feat: initial github-to-signal webhook server
HTTP server that receives GitHub webhook events and sends formatted notifications to Signal via signal-cli's JSON-RPC API. Supported events: push, issues, issue comments, pull requests, PR reviews, PR review comments, releases, stars, forks, workflow runs, branch/tag creation, branch/tag deletion. Uses cbrgm/githubevents for webhook handling and taigrr/signalcli for Signal delivery. Config via TOML file or GH2SIG_ env vars (powered by taigrr/jety).
This commit is contained in:
149
main.go
Normal file
149
main.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// github-to-signal is an HTTP server that receives GitHub webhook events
|
||||
// and forwards formatted notifications to Signal via signal-cli's JSON-RPC API.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/cbrgm/githubevents/v2/githubevents"
|
||||
"github.com/google/go-github/v70/github"
|
||||
"github.com/taigrr/signalcli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
if cfg.SignalAccount == "" {
|
||||
log.Fatal("signal_account is required (set GH2SIG_SIGNAL_ACCOUNT or config.toml)")
|
||||
}
|
||||
if cfg.SignalRecipient == "" {
|
||||
log.Fatal("signal_recipient is required (set GH2SIG_SIGNAL_RECIPIENT or config.toml)")
|
||||
}
|
||||
|
||||
signal := signalcli.NewClient(cfg.SignalURL, cfg.SignalAccount)
|
||||
handle := githubevents.New(cfg.WebhookSecret)
|
||||
|
||||
notifier := ¬ifier{
|
||||
signal: signal,
|
||||
recipient: cfg.SignalRecipient,
|
||||
}
|
||||
|
||||
// Register event handlers.
|
||||
handle.OnPushEventAny(notifier.onPush)
|
||||
handle.OnIssuesEventAny(notifier.onIssue)
|
||||
handle.OnIssueCommentEventAny(notifier.onIssueComment)
|
||||
handle.OnPullRequestEventAny(notifier.onPR)
|
||||
handle.OnPullRequestReviewEventAny(notifier.onPRReview)
|
||||
handle.OnPullRequestReviewCommentEventAny(notifier.onPRReviewComment)
|
||||
handle.OnReleaseEventAny(notifier.onRelease)
|
||||
handle.OnStarEventAny(notifier.onStar)
|
||||
handle.OnForkEventAny(notifier.onFork)
|
||||
handle.OnWorkflowRunEventAny(notifier.onWorkflowRun)
|
||||
handle.OnCreateEventAny(notifier.onCreate)
|
||||
handle.OnDeleteEventAny(notifier.onDelete)
|
||||
|
||||
handle.OnError(func(_ context.Context, _ string, _ string, _ interface{}, err error) error {
|
||||
log.Printf("webhook error: %v", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /webhook", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := handle.HandleEventRequest(r); err != nil {
|
||||
log.Printf("handle event: %v", err)
|
||||
http.Error(w, "webhook processing failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, "ok")
|
||||
})
|
||||
|
||||
log.Printf("listening on %s", cfg.ListenAddr)
|
||||
if err := http.ListenAndServe(cfg.ListenAddr, mux); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type notifier struct {
|
||||
signal *signalcli.Client
|
||||
recipient string
|
||||
}
|
||||
|
||||
func (n *notifier) send(ctx context.Context, msg string) {
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
_, err := n.signal.Send(ctx, signalcli.SendParams{
|
||||
Recipient: n.recipient,
|
||||
Message: msg,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("signal send error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *notifier) onPush(ctx context.Context, _ string, _ string, event *github.PushEvent) error {
|
||||
n.send(ctx, formatPush(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onIssue(ctx context.Context, _ string, _ string, event *github.IssuesEvent) error {
|
||||
n.send(ctx, formatIssue(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onIssueComment(ctx context.Context, _ string, _ string, event *github.IssueCommentEvent) error {
|
||||
n.send(ctx, formatIssueComment(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onPR(ctx context.Context, _ string, _ string, event *github.PullRequestEvent) error {
|
||||
n.send(ctx, formatPR(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onPRReview(ctx context.Context, _ string, _ string, event *github.PullRequestReviewEvent) error {
|
||||
n.send(ctx, formatPRReview(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onPRReviewComment(ctx context.Context, _ string, _ string, event *github.PullRequestReviewCommentEvent) error {
|
||||
n.send(ctx, formatPRReviewComment(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onRelease(ctx context.Context, _ string, _ string, event *github.ReleaseEvent) error {
|
||||
n.send(ctx, formatRelease(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onStar(ctx context.Context, _ string, _ string, event *github.StarEvent) error {
|
||||
n.send(ctx, formatStar(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onFork(ctx context.Context, _ string, _ string, event *github.ForkEvent) error {
|
||||
n.send(ctx, formatFork(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onWorkflowRun(ctx context.Context, _ string, _ string, event *github.WorkflowRunEvent) error {
|
||||
n.send(ctx, formatWorkflowRun(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onCreate(ctx context.Context, _ string, _ string, event *github.CreateEvent) error {
|
||||
n.send(ctx, formatCreate(event))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notifier) onDelete(ctx context.Context, _ string, _ string, event *github.DeleteEvent) error {
|
||||
n.send(ctx, formatDelete(event))
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user