From f1f5fb3c12f9afdb9f1e2abd287cd1186ea91545 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 26 Oct 2019 17:39:01 -0400 Subject: [PATCH] git: add nicer repo addition & gitea support Also added an `!unset` command for configurations. --- config/config.go | 17 +++++++++ plugins/admin/admin.go | 12 ++++++- plugins/git/git.go | 82 +++++++++++++++++++++++++++++++++++++++--- plugins/git/gitea.go | 68 +++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 plugins/git/gitea.go diff --git a/config/config.go b/config/config.go index ab68c06..5a43af7 100644 --- a/config/config.go +++ b/config/config.go @@ -108,6 +108,23 @@ func (c *Config) GetArray(key string, fallback []string) []string { return strings.Split(val, ";;") } +func (c *Config) Unset(key string) error { + q := `delete from config where key=?` + tx, err := c.Begin() + if err != nil { + return err + } + _, err = tx.Exec(q, key) + if err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil +} + // Set changes the value for a configuration in the database // Note, this is always a string. Use the SetArray for an array helper func (c *Config) Set(key, value string) error { diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 094ec76..d64989d 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -90,8 +90,18 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") return true + } else if parts[0] == "unset" && len(parts) > 1 { + if err := p.cfg.Unset(parts[1]); err != nil { + p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Unset error: %s", err)) + return true + } + p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Unset %s", parts[1])) + return true } else if parts[0] == "set" && len(parts) > 2 { - p.cfg.Set(parts[1], strings.Join(parts[2:], " ")) + if err := p.cfg.Set(parts[1], strings.Join(parts[2:], " ")); err != nil { + p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set error: %s", err)) + return true + } p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) return true } diff --git a/plugins/git/git.go b/plugins/git/git.go index 943600f..8f782a8 100644 --- a/plugins/git/git.go +++ b/plugins/git/git.go @@ -1,10 +1,13 @@ package git import ( + "encoding/json" "fmt" "net/http" "strings" + "github.com/velour/catbase/bot/msg" + "github.com/rs/zerolog/log" "gopkg.in/go-playground/webhooks.v5/github" "gopkg.in/go-playground/webhooks.v5/gitlab" @@ -38,15 +41,81 @@ func New(b bot.Bot) *GitPlugin { ghhook: ghhook, } p.registerWeb() + b.Register(p, bot.Message, p.message) return p } +func validService(service string) bool { + return service == "gitea" || service == "gitlab" || service == "github" +} + +func (p *GitPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + body := message.Body + lower := strings.ToLower(body) + parts := strings.Split(lower, " ") + + if !strings.HasPrefix(lower, "regrepo") || len(parts) != 2 { + return false + } + u := parts[1] + u = strings.ReplaceAll(u, "/", ".") + uparts := strings.Split(u, ".") + if len(uparts) != 3 || !validService(uparts[0]) { + m := "Valid formats are: `service.owner.repo` and valid services are one of gitea, gitlab, or github." + p.b.Send(c, bot.Message, message.Channel, m) + return true + } + chs := p.c.GetArray(u+".channels", []string{}) + for _, ch := range chs { + if ch == message.Channel { + p.b.Send(c, bot.Message, message.Channel, "That's already registered here.") + return true + } + } + chs = append(chs, message.Channel) + p.c.SetArray(u+".channels", chs) + p.b.Send(c, bot.Message, message.Channel, "Registered new repository.") + return true +} + func (p *GitPlugin) registerWeb() { + http.HandleFunc("/git/gitea/event", p.giteaEvent) http.HandleFunc("/git/github/event", p.githubEvent) http.HandleFunc("/git/gitlab/event", p.gitlabEvent) p.b.RegisterWeb("/git", "Git") } +func (p *GitPlugin) giteaEvent(w http.ResponseWriter, r *http.Request) { + evt := GiteaPush{} + dec := json.NewDecoder(r.Body) + err := dec.Decode(&evt) + if err != nil { + log.Error().Err(err).Msg("could not decode gitea push") + w.WriteHeader(500) + fmt.Fprintf(w, "Error parsing event: %s", err) + return + } + org := evt.Repository.Owner.Username + repo := evt.Repository.Name + + msg := "" + for _, c := range evt.Commits { + m := strings.Split(c.Message, "\n")[0] + msg += fmt.Sprintf("%s pushed to %s (<%s|%s>) %s", + c.Author.Name, + repo, + c.URL, + c.ID[:7], + m, + ) + } + + chs := p.c.GetArray(fmt.Sprintf("gitea.%s.%s.channels", org, repo), []string{}) + for _, ch := range chs { + p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) + } +} + func (p *GitPlugin) gitlabEvent(w http.ResponseWriter, r *http.Request) { if p.glhook == nil { log.Error().Msg("gitlab hook not initialized") @@ -63,11 +132,12 @@ func (p *GitPlugin) gitlabEvent(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "unknown event: %s", err) return } - msg, repo := "", "" + msg, repo, owner := "", "", "" switch payload.(type) { case gitlab.PushEventPayload: push := payload.(gitlab.PushEventPayload) repo = push.Repository.Name + owner = strings.ReplaceAll(push.Project.PathWithNamespace, "/", ".") commits := "" for _, c := range push.Commits { m := strings.Split(c.Message, "\n")[0] @@ -85,7 +155,7 @@ func (p *GitPlugin) gitlabEvent(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "unknown payload: %+v", payload) return } - chs := p.c.GetArray(fmt.Sprintf("gitlab.%s.channels", repo), []string{}) + chs := p.c.GetArray(fmt.Sprintf("gitlab.%s.channels", owner), []string{}) for _, ch := range chs { p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) } @@ -109,12 +179,12 @@ func (p *GitPlugin) githubEvent(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "unknown event: %+v", err) return } - msg := "" - repo := "" + msg, repo, owner := "", "", "" switch payload.(type) { case github.PushPayload: push := payload.(github.PushPayload) repo = push.Repository.Name + owner = push.Repository.Owner.Login commits := "" for _, c := range push.Commits { m := strings.Split(c.Message, "\n")[0] @@ -135,6 +205,7 @@ func (p *GitPlugin) githubEvent(w http.ResponseWriter, r *http.Request) { return } repo = pr.Repository.Name + owner = pr.Repository.Owner.Login msg = fmt.Sprintf("%s opened new pull request \"%s\" on %s: %s", pr.PullRequest.User.Login, pr.PullRequest.Title, @@ -144,6 +215,7 @@ func (p *GitPlugin) githubEvent(w http.ResponseWriter, r *http.Request) { case github.PingPayload: ping := payload.(github.PingPayload) repo = ping.Repository.Name + owner = ping.Repository.Owner.Login msg = fmt.Sprintf("Got a ping request on %s", repo) default: log.Error().Interface("payload", payload).Msg("unknown event payload") @@ -152,7 +224,7 @@ func (p *GitPlugin) githubEvent(w http.ResponseWriter, r *http.Request) { return } - chs := p.c.GetArray(fmt.Sprintf("github.%s.channels", repo), []string{}) + chs := p.c.GetArray(fmt.Sprintf("github.%s.%s.channels", owner, repo), []string{}) for _, ch := range chs { p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) } diff --git a/plugins/git/gitea.go b/plugins/git/gitea.go new file mode 100644 index 0000000..a102478 --- /dev/null +++ b/plugins/git/gitea.go @@ -0,0 +1,68 @@ +package git + +type GiteaPush struct { + Secret string `json:"secret"` + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + CompareURL string `json:"compare_url"` + Commits []struct { + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + } `json:"author"` + Committer struct { + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + } `json:"committer"` + Timestamp string `json:"timestamp"` + } `json:"commits"` + Repository struct { + ID int `json:"id"` + Owner struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Private bool `json:"private"` + Fork bool `json:"fork"` + HTMLURL string `json:"html_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + Website string `json:"website"` + StarsCount int `json:"stars_count"` + ForksCount int `json:"forks_count"` + WatchersCount int `json:"watchers_count"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + } `json:"repository"` + Pusher struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"pusher"` + Sender struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"sender"` +}