diff --git a/src/github.com/matrix-org/go-neb/services/github/github.go b/src/github.com/matrix-org/go-neb/services/github/github.go index 347656a..866f5d5 100644 --- a/src/github.com/matrix-org/go-neb/services/github/github.go +++ b/src/github.com/matrix-org/go-neb/services/github/github.go @@ -27,6 +27,9 @@ const ServiceType = "github" // Matches alphanumeric then a /, then more alphanumeric then a #, then a number. // E.g. owner/repo#11 (issue/PR numbers) - Captured groups for owner/repo/number var ownerRepoIssueRegex = regexp.MustCompile(`(([A-z0-9-_.]+)/([A-z0-9-_.]+))?#([0-9]+)`) + +// Matches like above, but anchored to start and end of the string respectively. +var ownerRepoIssueRegexAnchored = regexp.MustCompile(`^(([A-z0-9-_.]+)/([A-z0-9-_.]+))?#([0-9]+)$`) var ownerRepoRegex = regexp.MustCompile(`^([A-z0-9-_.]+)/([A-z0-9-_.]+)$`) // Service contains the Config fields for the Github service. @@ -55,21 +58,29 @@ type Service struct { RealmID string } -func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interface{}, error) { - cli := s.githubClientFor(userID, false) +func (s *Service) requireGithubClientFor(userID string) (cli *gogithub.Client, resp interface{}, err error) { + cli = s.githubClientFor(userID, false) if cli == nil { - r, err := database.GetServiceDB().LoadAuthRealm(s.RealmID) - if err != nil { - return nil, err + var r types.AuthRealm + if r, err = database.GetServiceDB().LoadAuthRealm(s.RealmID); err != nil { + return } - ghRealm, ok := r.(*github.Realm) - if !ok { - return nil, fmt.Errorf("Failed to cast realm %s into a GithubRealm", s.RealmID) + if ghRealm, ok := r.(*github.Realm); ok { + resp = matrix.StarterLinkMessage{ + Body: "You need to log into Github before you can create issues.", + Link: ghRealm.StarterLink, + } + } else { + err = fmt.Errorf("Failed to cast realm %s into a GithubRealm", s.RealmID) } - return matrix.StarterLinkMessage{ - Body: "You need to log into Github before you can create issues.", - Link: ghRealm.StarterLink, - }, nil + } + return +} + +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", @@ -131,6 +142,81 @@ 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) { + 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 + } + + // 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]) + + if len(ownerRepoIssueGroups) != 5 { + return &gomatrix.TextMessage{"m.notice", + `Usage: !github comment [owner/repo]#issue "comment text"`}, 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 + } + + 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 + } + + // 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], + } + } + + 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 + } + + var comment *string + + if len(args) == 2 { + comment = &args[1] + } else { // > 2 args is probably a comment without quote marks + joinedComment := strings.Join(args[1:], " ") + comment = &joinedComment + } + + issueComment, res, err := cli.Issues.CreateComment(ownerRepoIssueGroups[2], ownerRepoIssueGroups[3], issueNum, &gogithub.IssueComment{ + Body: comment, + }) + + if err != nil { + log.WithField("err", err).Print("Failed to create issue") + if res == nil { + return nil, fmt.Errorf("Failed to create issue. Failed to connect to Github") + } + return nil, fmt.Errorf("Failed to create issue. HTTP %d", res.StatusCode) + } + + return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Commented on issue: %s", *issueComment.HTMLURL)}, nil +} + func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int) interface{} { cli := s.githubClientFor(userID, true) @@ -155,6 +241,10 @@ func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int) // Responds with the outcome of the issue creation request. This command requires // a Github account to be linked to the Matrix user ID issuing the command. If there // is no link, it will return a Starter Link instead. +// !github comment [owner/repo]#issue "comment" +// Responds with the outcome of the issue comment creation request. This command requires +// a Github account to be linked to the Matrix user ID issuing the command. If there +// is no link, it will return a Starter Link instead. func (s *Service) Commands(cli *gomatrix.Client) []types.Command { return []types.Command{ types.Command{ @@ -163,12 +253,19 @@ func (s *Service) Commands(cli *gomatrix.Client) []types.Command { return s.cmdGithubCreate(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", "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"`), + fmt.Sprintf(`!github create owner/repo "title text" "description text"` + "\n" + + `!github comment [owner/repo]#issue "comment text"`), }, nil }, },