Browse Source

Merge pull request #197 from t3chguy/moar-github-commands

Moar GitHub commands
t3chguy/github_regex_tweaks
Matthew Hodgson 7 years ago
committed by GitHub
parent
commit
f1068a2014
  1. 312
      src/github.com/matrix-org/go-neb/services/github/github.go

312
src/github.com/matrix-org/go-neb/services/github/github.go

@ -11,6 +11,7 @@ import (
"strconv"
"strings"
"bytes"
log "github.com/Sirupsen/logrus"
gogithub "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database"
@ -19,6 +20,7 @@ import (
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"html"
)
// ServiceType of the Github service
@ -77,14 +79,65 @@ func (s *Service) requireGithubClientFor(userID string) (cli *gogithub.Client, r
return
}
const numberGithubSearchSummaries = 3
const cmdGithubSearchUsage = `!github create owner/repo "search query"`
func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interface{}, error) {
cli := s.githubClientFor(userID, true)
if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubSearchUsage}, nil
}
query := fmt.Sprintf("repo:%s %s", args[0], strings.Join(args[1:], " "))
searchResult, res, err := cli.Search.Issues(query, nil)
if err != nil {
log.WithField("err", err).Print("Failed to search")
if res == nil {
return nil, fmt.Errorf("Failed to search. Failed to connect to Github")
}
return nil, fmt.Errorf("Failed to search. HTTP %d", res.StatusCode)
}
if searchResult.Total == nil || *searchResult.Total == 0 {
return &gomatrix.TextMessage{"m.notice", "No results found for your search query!"}, nil
}
numResults := *searchResult.Total
var htmlBuffer bytes.Buffer
var plainBuffer bytes.Buffer
htmlBuffer.WriteString(fmt.Sprintf("Found %d results, here are the most relevant:<br><ol>", numResults))
plainBuffer.WriteString(fmt.Sprintf("Found %d results, here are the most relevant:\n", numResults))
for i, issue := range searchResult.Issues {
if i >= numberGithubSearchSummaries {
break
}
if issue.HTMLURL == nil || issue.User.Login == nil || issue.Title == nil {
continue
}
escapedTitle, escapedUserLogin := html.EscapeString(*issue.Title), html.EscapeString(*issue.User.Login)
htmlBuffer.WriteString(fmt.Sprintf(`<li><a href="%s" rel="noopener">%s: %s</a></li>`, *issue.HTMLURL, escapedUserLogin, escapedTitle))
plainBuffer.WriteString(fmt.Sprintf("%d. %s\n", i+1, *issue.HTMLURL))
}
htmlBuffer.WriteString("</ol>")
return &gomatrix.HTMLMessage{
Body: plainBuffer.String(),
MsgType: "m.notice",
Format: "org.matrix.custom.html",
FormattedBody: htmlBuffer.String(),
}, nil
}
const cmdGithubCreateUsage = `!github create [owner/repo] "issue title" "description"`
func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) == 0 {
return &gomatrix.TextMessage{"m.notice",
`Usage: !github create owner/repo "issue title" "description"`}, nil
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubCreateUsage}, nil
}
// We expect the args to look like:
@ -97,14 +150,12 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
// look for a default repo
defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" {
return &gomatrix.TextMessage{"m.notice",
`Usage: !github create owner/repo "issue title" "description"`}, nil
return &gomatrix.TextMessage{"m.notice", "Need to specify repo. Usage: " + cmdGithubCreateUsage}, nil
}
// default repo should pass the regexp
ownerRepoGroups = ownerRepoRegex.FindStringSubmatch(defaultRepo)
if len(ownerRepoGroups) == 0 {
return &gomatrix.TextMessage{"m.notice",
`Malformed default repo. Usage: !github create owner/repo "issue title" "description"`}, nil
return &gomatrix.TextMessage{"m.notice", "Malformed default repo. Usage: " + cmdGithubCreateUsage}, nil
}
// insert the default as the first arg to reuse the same indices
@ -142,55 +193,87 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Created issue: %s", *issue.HTMLURL)}, nil
}
func (s *Service) cmdGithubComment(roomID, userID string, args []string) (interface{}, error) {
var cmdGithubReactAliases = map[string]string{
"+1": "+1",
":+1:": "+1",
"👍": "+1",
"-1": "-1",
":-1:": "-1",
"👎": "-1",
"laugh": "laugh",
"smile": "laugh",
":smile:": "laugh",
"😄": "laugh",
"grin": "laugh",
"confused": "confused",
":confused:": "confused",
"😕": "confused",
"uncertain": "confused",
"heart": "heart",
":heart:": "heart",
"❤": "heart",
"❤️": "heart",
"hooray": "hooray",
"tada": "hooray",
":tada:": "hooray",
"🎉": "hooray",
}
const cmdGithubReactUsage = `!github react [owner/repo]#issue (+1|👍|-1|:-1:|laugh|:smile:|confused|uncertain|heart|❤|hooray|:tada:)`
func (s *Service) cmdGithubReact(roomID, userID string, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) == 0 {
return &gomatrix.TextMessage{"m.notice",
`Usage: !github comment [owner/repo]#issue "comment text"`}, nil
if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubReactUsage}, nil
}
// We expect the args to look like:
// [ "[owner/repo]#issue", "comment" ]
// They can omit the owner/repo if there is a default one set.
// Look for a default if the first arg is just an issue number
ownerRepoIssueGroups := ownerRepoIssueRegexAnchored.FindStringSubmatch(args[0])
reaction, ok := cmdGithubReactAliases[args[1]]
if !ok {
return &gomatrix.TextMessage{"m.notice", "Invalid reaction. Usage: " + cmdGithubReactUsage}, nil
}
if len(ownerRepoIssueGroups) != 5 {
return &gomatrix.TextMessage{"m.notice",
`Usage: !github comment [owner/repo]#issue "comment text"`}, nil
// get owner,repo,issue,resp out of args[0]
owner, repo, issueNum, resp := s.getIssueDetailsFor(args[0], roomID, cmdGithubReactUsage)
if resp != nil {
return resp, nil
}
if ownerRepoIssueGroups[1] == "" {
// issue only match, this only works if there is a default repo
defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" {
return &gomatrix.TextMessage{"m.notice",
`Usage: !github comment [owner/repo]#issue "comment text"`}, nil
}
_, res, err := cli.Reactions.CreateIssueReaction(owner, repo, issueNum, reaction)
segs := strings.Split(defaultRepo, "/")
if len(segs) != 2 {
return &gomatrix.TextMessage{"m.notice",
`Malformed default repo. Usage: !github comment [owner/repo]#issue "comment text"`}, nil
if err != nil {
log.WithField("err", err).Print("Failed to react to issue")
if res == nil {
return nil, fmt.Errorf("Failed to react to issue. Failed to connect to Github")
}
return nil, fmt.Errorf("Failed to react to issue. HTTP %d", res.StatusCode)
}
// Fill in the missing fields in matching groups and fall through into ["foo/bar#11", "foo", "bar", "11"]
ownerRepoIssueGroups = []string{
defaultRepo + ownerRepoIssueGroups[0],
defaultRepo,
segs[0],
segs[1],
ownerRepoIssueGroups[4],
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Reacted to issue with: %s", args[1])}, nil
}
const cmdGithubCommentUsage = `!github comment [owner/repo]#issue "comment text"`
func (s *Service) cmdGithubComment(roomID, userID string, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) == 0 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubCommentUsage}, nil
}
issueNum, err := strconv.Atoi(ownerRepoIssueGroups[4])
if err != nil {
return &gomatrix.TextMessage{"m.notice",
`Malformed issue number. Usage: !github comment [owner/repo]#issue "comment text"`}, nil
// get owner,repo,issue,resp out of args[0]
owner, repo, issueNum, resp := s.getIssueDetailsFor(args[0], roomID, cmdGithubCommentUsage)
if resp != nil {
return resp, nil
}
var comment *string
@ -202,21 +285,127 @@ func (s *Service) cmdGithubComment(roomID, userID string, args []string) (interf
comment = &joinedComment
}
issueComment, res, err := cli.Issues.CreateComment(ownerRepoIssueGroups[2], ownerRepoIssueGroups[3], issueNum, &gogithub.IssueComment{
issueComment, res, err := cli.Issues.CreateComment(owner, repo, issueNum, &gogithub.IssueComment{
Body: comment,
})
if err != nil {
log.WithField("err", err).Print("Failed to create issue")
log.WithField("err", err).Print("Failed to create issue comment")
if res == nil {
return nil, fmt.Errorf("Failed to create issue. Failed to connect to Github")
return nil, fmt.Errorf("Failed to create issue comment. Failed to connect to Github")
}
return nil, fmt.Errorf("Failed to create issue. HTTP %d", res.StatusCode)
return nil, fmt.Errorf("Failed to create issue comment. HTTP %d", res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Commented on issue: %s", *issueComment.HTMLURL)}, nil
}
const cmdGithubAssignUsage = `!github assign [owner/repo]#issue username [username] [...]`
func (s *Service) cmdGithubAssign(roomID, userID string, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) < 1 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubAssignUsage}, nil
} else if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Needs at least one username. Usage: " + cmdGithubAssignUsage}, nil
}
// get owner,repo,issue,resp out of args[0]
owner, repo, issueNum, resp := s.getIssueDetailsFor(args[0], roomID, cmdGithubAssignUsage)
if resp != nil {
return resp, nil
}
issue, res, err := cli.Issues.AddAssignees(owner, repo, issueNum, args[1:])
if err != nil {
log.WithField("err", err).Print("Failed to add issue assignees")
if res == nil {
return nil, fmt.Errorf("Failed to add issue assignees. Failed to connect to Github")
}
return nil, fmt.Errorf("Failed to add issue assignees. HTTP %d", res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Added assignees to issue: %s", *issue.HTMLURL)}, nil
}
const cmdGithubCloseUsage = `!github close [owner/repo]#issue`
func (s *Service) cmdGithubClose(roomID, userID string, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) == 0 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubCloseUsage}, nil
}
// get owner,repo,issue,resp out of args[0]
owner, repo, issueNum, resp := s.getIssueDetailsFor(args[0], roomID, cmdGithubCloseUsage)
if resp != nil {
return resp, nil
}
state := "closed"
issueComment, res, err := cli.Issues.Edit(owner, repo, issueNum, &gogithub.IssueRequest{
State: &state,
})
if err != nil {
log.WithField("err", err).Print("Failed to close issue")
if res == nil {
return nil, fmt.Errorf("Failed to close issue. Failed to connect to Github")
}
return nil, fmt.Errorf("Failed to close issue. HTTP %d", res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Closed issue: %s", *issueComment.HTMLURL)}, nil
}
func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo string, issueNum int, resp interface{}) {
// We expect the input to look like:
// "[owner/repo]#issue"
// They can omit the owner/repo if there is a default one set.
// Look for a default if the first arg is just an issue number
ownerRepoIssueGroups := ownerRepoIssueRegexAnchored.FindStringSubmatch(input)
if len(ownerRepoIssueGroups) != 5 {
resp = &gomatrix.TextMessage{"m.notice", "Usage: " + usage}
return
}
owner = ownerRepoIssueGroups[2]
repo = ownerRepoIssueGroups[3]
var err error
if issueNum, err = strconv.Atoi(ownerRepoIssueGroups[4]); err != nil {
resp = &gomatrix.TextMessage{"m.notice", "Malformed issue number. Usage: " + usage}
return
}
if ownerRepoIssueGroups[1] == "" {
// issue only match, this only works if there is a default repo
defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" {
resp = &gomatrix.TextMessage{"m.notice", "Need to specify repo. Usage: " + usage}
return
}
segs := strings.Split(defaultRepo, "/")
if len(segs) != 2 {
resp = &gomatrix.TextMessage{"m.notice", "Malformed default repo. Usage: " + usage}
return
}
owner = segs[0]
repo = segs[1]
}
return
}
func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int) interface{} {
cli := s.githubClientFor(userID, true)
@ -247,25 +436,54 @@ func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int)
// is no link, it will return a Starter Link instead.
func (s *Service) Commands(cli *gomatrix.Client) []types.Command {
return []types.Command{
types.Command{
Path: []string{"github", "search"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubSearch(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubCreate(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "react"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubReact(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "comment"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubComment(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "assign"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubAssign(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "close"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubClose(roomID, userID, args)
},
},
types.Command{
Path: []string{"github", "help"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return &gomatrix.TextMessage{
"m.notice",
fmt.Sprintf(`!github create owner/repo "title text" "description text"` + "\n" +
`!github comment [owner/repo]#issue "comment text"`),
strings.Join([]string{
cmdGithubCreateUsage,
cmdGithubReactUsage,
cmdGithubCommentUsage,
cmdGithubAssignUsage,
cmdGithubCloseUsage,
}, "\n"),
}, nil
},
},

Loading…
Cancel
Save