Browse Source

Convert and re-enable Github realm and service, and update go-github

version

Signed-off-by: Nikos Filippakis <me@nfil.dev>
pull/322/head
Nikos Filippakis 4 years ago
parent
commit
69d063f0ec
  1. 2
      go.mod
  2. 6
      goneb.go
  3. 14
      realms/github/github.go
  4. 215
      services/github/github.go
  5. 29
      services/github/github_webhook.go
  6. 9
      services/github/github_webhook_test.go
  7. 7
      services/github/webhook/webhook.go
  8. 21
      services/utils/utils.go
  9. 19
      services/utils/utils_test.go

2
go.mod

@ -17,7 +17,7 @@ require (
github.com/gogo/protobuf v1.1.1 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.4.0 // indirect
github.com/google/go-github v2.0.1-0.20160719063544-b5e5babef39c+incompatible
github.com/google/go-github v17.0.0+incompatible
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195
github.com/json-iterator/go v1.1.9 // indirect

6
goneb.go

@ -17,14 +17,14 @@ import (
"github.com/matrix-org/go-neb/database"
_ "github.com/matrix-org/go-neb/metrics"
"github.com/matrix-org/go-neb/polling"
//_ "github.com/matrix-org/go-neb/realms/github"
_ "github.com/matrix-org/go-neb/realms/github"
_ "github.com/matrix-org/go-neb/realms/jira"
//_ "github.com/matrix-org/go-neb/services/alertmanager"
_ "github.com/matrix-org/go-neb/services/echo"
_ "github.com/matrix-org/go-neb/services/giphy"
_ "github.com/matrix-org/go-neb/services/github"
//_ "github.com/matrix-org/go-neb/services/github"
//_ "github.com/matrix-org/go-neb/services/google"
_ "github.com/matrix-org/go-neb/services/guggy"
_ "github.com/matrix-org/go-neb/services/imgur"

14
realms/github/github.go

@ -2,6 +2,7 @@
package github
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
@ -14,6 +15,7 @@ import (
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix/id"
)
// RealmType of the Github Realm
@ -41,7 +43,7 @@ type Realm struct {
// Session represents an authenticated github session
type Session struct {
id string
userID string
userID id.UserID
realmID string
// AccessToken is the github access token for the user
@ -86,7 +88,7 @@ func (s *Session) Info() interface{} {
}
for {
// query for a list of possible projects
rs, resp, err := cli.Repositories.List("", opts)
rs, resp, err := cli.Repositories.List(context.Background(), "", opts)
if err != nil {
logger.WithError(err).Print("Failed to query github projects on github.com")
return nil
@ -110,7 +112,7 @@ func (s *Session) Info() interface{} {
}
// UserID returns the user_id who authorised with Github
func (s *Session) UserID() string {
func (s *Session) UserID() id.UserID {
return s.userID
}
@ -156,7 +158,7 @@ func (r *Realm) Register() error {
// {
// "URL": "https://github.com/login/oauth/authorize?client_id=abcdef&client_secret=acascacac...."
// }
func (r *Realm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
func (r *Realm) RequestAuthSession(userID id.UserID, req json.RawMessage) interface{} {
state, err := randomString(10)
if err != nil {
log.WithError(err).Print("Failed to generate state param")
@ -259,7 +261,7 @@ func (r *Realm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
return
}
r.redirectOr(
w, 200, "You have successfully linked your Github account to "+ghSession.UserID(), logger, ghSession,
w, 200, "You have successfully linked your Github account to "+ghSession.UserID().String(), logger, ghSession,
)
}
@ -275,7 +277,7 @@ func (r *Realm) redirectOr(w http.ResponseWriter, code int, msg string, logger *
}
// AuthSession returns a Github Session for this user
func (r *Realm) AuthSession(id, userID, realmID string) types.AuthSession {
func (r *Realm) AuthSession(id string, userID id.UserID, realmID string) types.AuthSession {
return &Session{
id: id,
userID: userID,

215
services/github/github.go

@ -5,6 +5,7 @@
package github
import (
"context"
"database/sql"
"fmt"
"regexp"
@ -12,15 +13,18 @@ import (
"strings"
"bytes"
"html"
gogithub "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/realms/github"
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus"
"html"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// ServiceType of the Github service
@ -69,7 +73,7 @@ type Service struct {
RealmID string
}
func (s *Service) requireGithubClientFor(userID string) (cli *gogithub.Client, resp interface{}, err error) {
func (s *Service) requireGithubClientFor(userID id.UserID) (cli *gogithub.Client, resp interface{}, err error) {
cli = s.githubClientFor(userID, false)
if cli == nil {
var r types.AuthRealm
@ -91,14 +95,17 @@ func (s *Service) requireGithubClientFor(userID string) (cli *gogithub.Client, r
const numberGithubSearchSummaries = 3
const cmdGithubSearchUsage = `!github search "search query"`
func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGithubSearch(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
cli := s.githubClientFor(userID, true)
if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubSearchUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + cmdGithubSearchUsage,
}, nil
}
query := strings.Join(args, " ")
searchResult, res, err := cli.Search.Issues(query, nil)
searchResult, res, err := cli.Search.Issues(context.Background(), query, nil)
if err != nil {
log.WithField("err", err).Print("Failed to search")
@ -109,7 +116,10 @@ func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interfa
}
if searchResult.Total == nil || *searchResult.Total == 0 {
return &gomatrix.TextMessage{"m.notice", "No results found for your search query!"}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "No results found for your search query!",
}, nil
}
numResults := *searchResult.Total
@ -130,9 +140,9 @@ func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interfa
}
htmlBuffer.WriteString("</ol>")
return &gomatrix.HTMLMessage{
return &mevt.MessageEventContent{
Body: plainBuffer.String(),
MsgType: "m.notice",
MsgType: mevt.MsgNotice,
Format: "org.matrix.custom.html",
FormattedBody: htmlBuffer.String(),
}, nil
@ -140,13 +150,16 @@ func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interfa
const cmdGithubCreateUsage = `!github create [owner/repo] "issue title" "description"`
func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGithubCreate(roomID id.RoomID, userID id.UserID, 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: " + cmdGithubCreateUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + cmdGithubCreateUsage,
}, nil
}
// We expect the args to look like:
@ -159,12 +172,16 @@ 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", "Need to specify repo. Usage: " + cmdGithubCreateUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "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: " + cmdGithubCreateUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice, Body: "Malformed default repo. Usage: " + cmdGithubCreateUsage}, nil
}
// insert the default as the first arg to reuse the same indices
@ -187,7 +204,7 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
title = &joinedTitle
}
issue, res, err := cli.Issues.Create(ownerRepoGroups[1], ownerRepoGroups[2], &gogithub.IssueRequest{
issue, res, err := cli.Issues.Create(context.Background(), ownerRepoGroups[1], ownerRepoGroups[2], &gogithub.IssueRequest{
Title: title,
Body: desc,
})
@ -199,7 +216,8 @@ func (s *Service) cmdGithubCreate(roomID, userID string, args []string) (interfa
return nil, fmt.Errorf("Failed to create issue. HTTP %d", res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Created issue: %s", *issue.HTMLURL)}, nil
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice, Body: fmt.Sprintf("Created issue: %s", *issue.HTMLURL)}, nil
}
var cmdGithubReactAliases = map[string]string{
@ -235,18 +253,23 @@ var cmdGithubReactAliases = map[string]string{
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) {
func (s *Service) cmdGithubReact(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil {
return resp, err
}
if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Usage: " + cmdGithubReactUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice, Body: "Usage: " + cmdGithubReactUsage,
}, nil
}
reaction, ok := cmdGithubReactAliases[args[1]]
if !ok {
return &gomatrix.TextMessage{"m.notice", "Invalid reaction. Usage: " + cmdGithubReactUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Invalid reaction. Usage: " + cmdGithubReactUsage,
}, nil
}
// get owner,repo,issue,resp out of args[0]
@ -255,7 +278,7 @@ func (s *Service) cmdGithubReact(roomID, userID string, args []string) (interfac
return resp, nil
}
_, res, err := cli.Reactions.CreateIssueReaction(owner, repo, issueNum, reaction)
_, res, err := cli.Reactions.CreateIssueReaction(context.Background(), owner, repo, issueNum, reaction)
if err != nil {
log.WithField("err", err).Print("Failed to react to issue")
@ -265,18 +288,24 @@ func (s *Service) cmdGithubReact(roomID, userID string, args []string) (interfac
return nil, fmt.Errorf("Failed to react to issue. HTTP %d", res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Reacted to issue with: %s", args[1])}, nil
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: 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) {
func (s *Service) cmdGithubComment(roomID id.RoomID, userID id.UserID, 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
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + cmdGithubCommentUsage,
}, nil
}
// get owner,repo,issue,resp out of args[0]
@ -294,7 +323,7 @@ func (s *Service) cmdGithubComment(roomID, userID string, args []string) (interf
comment = &joinedComment
}
issueComment, res, err := cli.Issues.CreateComment(owner, repo, issueNum, &gogithub.IssueComment{
issueComment, res, err := cli.Issues.CreateComment(context.Background(), owner, repo, issueNum, &gogithub.IssueComment{
Body: comment,
})
@ -306,20 +335,29 @@ func (s *Service) cmdGithubComment(roomID, userID string, args []string) (interf
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
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: 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) {
func (s *Service) cmdGithubAssign(roomID id.RoomID, userID id.UserID, 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
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + cmdGithubAssignUsage,
}, nil
} else if len(args) < 2 {
return &gomatrix.TextMessage{"m.notice", "Needs at least one username. Usage: " + cmdGithubAssignUsage}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Needs at least one username. Usage: " + cmdGithubAssignUsage,
}, nil
}
// get owner,repo,issue,resp out of args[0]
@ -328,7 +366,7 @@ func (s *Service) cmdGithubAssign(roomID, userID string, args []string) (interfa
return resp, nil
}
issue, res, err := cli.Issues.AddAssignees(owner, repo, issueNum, args[1:])
issue, res, err := cli.Issues.AddAssignees(context.Background(), owner, repo, issueNum, args[1:])
if err != nil {
log.WithField("err", err).Print("Failed to add issue assignees")
@ -338,16 +376,22 @@ func (s *Service) cmdGithubAssign(roomID, userID string, args []string) (interfa
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
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: fmt.Sprintf("Added assignees to issue: %s", *issue.HTMLURL),
}, nil
}
func (s *Service) githubIssueCloseReopen(roomID, userID string, args []string, state, verb, help string) (interface{}, error) {
func (s *Service) githubIssueCloseReopen(roomID id.RoomID, userID id.UserID, args []string, state, verb, help 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: " + help}, nil
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + help,
}, nil
}
// get owner,repo,issue,resp out of args[0]
@ -356,7 +400,7 @@ func (s *Service) githubIssueCloseReopen(roomID, userID string, args []string, s
return resp, nil
}
issueComment, res, err := cli.Issues.Edit(owner, repo, issueNum, &gogithub.IssueRequest{
issueComment, res, err := cli.Issues.Edit(context.Background(), owner, repo, issueNum, &gogithub.IssueRequest{
State: &state,
})
@ -368,22 +412,25 @@ func (s *Service) githubIssueCloseReopen(roomID, userID string, args []string, s
return nil, fmt.Errorf("Failed to %s issue. HTTP %d", verb, res.StatusCode)
}
return gomatrix.TextMessage{"m.notice", fmt.Sprintf("Closed issue: %s", *issueComment.HTMLURL)}, nil
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: fmt.Sprintf("Closed issue: %s", *issueComment.HTMLURL),
}, nil
}
const cmdGithubCloseUsage = `!github close [owner/repo]#issue`
func (s *Service) cmdGithubClose(roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGithubClose(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.githubIssueCloseReopen(roomID, userID, args, "closed", "close", cmdGithubCloseUsage)
}
const cmdGithubReopenUsage = `!github reopen [owner/repo]#issue`
func (s *Service) cmdGithubReopen(roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGithubReopen(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.githubIssueCloseReopen(roomID, userID, args, "open", "open", cmdGithubCloseUsage)
}
func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo string, issueNum int, resp interface{}) {
func (s *Service) getIssueDetailsFor(input string, roomID id.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.
@ -391,7 +438,10 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
ownerRepoIssueGroups := ownerRepoIssueRegexAnchored.FindStringSubmatch(input)
if len(ownerRepoIssueGroups) != 5 {
resp = &gomatrix.TextMessage{"m.notice", "Usage: " + usage}
resp = &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + usage,
}
return
}
@ -400,7 +450,10 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
var err error
if issueNum, err = strconv.Atoi(ownerRepoIssueGroups[4]); err != nil {
resp = &gomatrix.TextMessage{"m.notice", "Malformed issue number. Usage: " + usage}
resp = &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Malformed issue number. Usage: " + usage,
}
return
}
@ -408,13 +461,19 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
// 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}
resp = &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "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}
resp = &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Malformed default repo. Usage: " + usage,
}
return
}
@ -424,10 +483,10 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
return
}
func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int) interface{} {
func (s *Service) expandIssue(roomID id.RoomID, userID id.UserID, owner, repo string, issueNum int) interface{} {
cli := s.githubClientFor(userID, true)
i, _, err := cli.Issues.Get(owner, repo, issueNum)
i, _, err := cli.Issues.Get(context.Background(), owner, repo, issueNum)
if err != nil {
log.WithError(err).WithFields(log.Fields{
"owner": owner,
@ -437,16 +496,16 @@ func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int)
return nil
}
return &gomatrix.TextMessage{
"m.notice",
fmt.Sprintf("%s : %s", *i.HTMLURL, *i.Title),
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: fmt.Sprintf("%s : %s", *i.HTMLURL, *i.Title),
}
}
func (s *Service) expandCommit(roomID, userID, owner, repo, sha string) interface{} {
func (s *Service) expandCommit(roomID id.RoomID, userID id.UserID, owner, repo, sha string) interface{} {
cli := s.githubClientFor(userID, true)
c, _, err := cli.Repositories.GetCommit(owner, repo, sha)
c, _, err := cli.Repositories.GetCommit(context.Background(), owner, repo, sha)
if err != nil {
log.WithError(err).WithFields(log.Fields{
"owner": owner,
@ -487,10 +546,10 @@ func (s *Service) expandCommit(roomID, userID, owner, repo, sha string) interfac
plainBuffer.WriteString(segs[0])
}
return &gomatrix.HTMLMessage{
return &mevt.MessageEventContent{
Body: plainBuffer.String(),
MsgType: "m.notice",
Format: "org.matrix.custom.html",
MsgType: mevt.MsgNotice,
Format: mevt.FormatHTML,
FormattedBody: htmlBuffer.String(),
}
}
@ -504,56 +563,56 @@ func (s *Service) expandCommit(roomID, userID, owner, repo, sha string) interfac
// 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 {
func (s *Service) Commands(cli *mautrix.Client) []types.Command {
return []types.Command{
types.Command{
{
Path: []string{"github", "search"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, 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) {
Command: func(roomID id.RoomID, userID id.UserID, 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) {
Command: func(roomID id.RoomID, userID id.UserID, 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) {
Command: func(roomID id.RoomID, userID id.UserID, 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) {
Command: func(roomID id.RoomID, userID id.UserID, 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) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdGithubClose(roomID, userID, args)
},
},
types.Command{
{
Path: []string{"github", "reopen"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdGithubReopen(roomID, userID, args)
},
},
types.Command{
{
Path: []string{"github", "help"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return &gomatrix.TextMessage{
"m.notice",
strings.Join([]string{
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: strings.Join([]string{
cmdGithubCreateUsage,
cmdGithubReactUsage,
cmdGithubCommentUsage,
@ -573,11 +632,11 @@ func (s *Service) Commands(cli *gomatrix.Client) []types.Command {
// it will also expand strings of the form:
// #12
// using the default repository.
func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
return []types.Expansion{
types.Expansion{
Regexp: ownerRepoIssueRegex,
Expand: func(roomID, userID string, matchingGroups []string) interface{} {
Expand: func(roomID id.RoomID, userID id.UserID, matchingGroups []string) interface{} {
// There's an optional group in the regex so matchingGroups can look like:
// [foo/bar#55 foo bar 55]
// [#55 55]
@ -619,7 +678,7 @@ func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
},
types.Expansion{
Regexp: ownerRepoCommitRegex,
Expand: func(roomID, userID string, matchingGroups []string) interface{} {
Expand: func(roomID id.RoomID, userID id.UserID, matchingGroups []string) interface{} {
// There's an optional group in the regex so matchingGroups can look like:
// [foo/bar@a123 foo bar a123]
// [@a123 a123]
@ -659,7 +718,7 @@ func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
}
// Register makes sure that the given realm ID maps to a github realm.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
if s.RealmID == "" {
return fmt.Errorf("RealmID is required")
}
@ -678,7 +737,7 @@ func (s *Service) Register(oldService types.Service, client *gomatrix.Client) er
}
// defaultRepo returns the default repo for the given room, or an empty string.
func (s *Service) defaultRepo(roomID string) string {
func (s *Service) defaultRepo(roomID id.RoomID) string {
logger := log.WithFields(log.Fields{
"room_id": roomID,
"bot_user_id": s.ServiceUserID(),
@ -707,7 +766,7 @@ func (s *Service) defaultRepo(roomID string) string {
return defaultRepo
}
func (s *Service) githubClientFor(userID string, allowUnauth bool) *gogithub.Client {
func (s *Service) githubClientFor(userID id.UserID, allowUnauth bool) *gogithub.Client {
token, err := getTokenForUser(s.RealmID, userID)
if err != nil {
log.WithFields(log.Fields{
@ -725,7 +784,7 @@ func (s *Service) githubClientFor(userID string, allowUnauth bool) *gogithub.Cli
}
}
func getTokenForUser(realmID, userID string) (string, error) {
func getTokenForUser(realmID string, userID id.UserID) (string, error) {
realm, err := database.GetServiceDB().LoadAuthRealm(realmID)
if err != nil {
return "", err
@ -750,7 +809,7 @@ func getTokenForUser(realmID, userID string) (string, error) {
}
func init() {
types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
types.RegisterService(func(serviceID string, serviceUserID id.UserID, webhookEndpointURL string) types.Service {
return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
}

29
services/github/github_webhook.go

@ -1,6 +1,7 @@
package github
import (
"context"
"fmt"
"net/http"
"sort"
@ -11,8 +12,10 @@ import (
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/services/github/webhook"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// WebhookServiceType of the Github Webhook service.
@ -45,12 +48,12 @@ type WebhookService struct {
types.DefaultService
webhookEndpointURL string
// The user ID to create/delete webhooks as.
ClientUserID string
ClientUserID id.UserID
// The ID of an existing "github" realm. This realm will be used to obtain
// the Github credentials of the ClientUserID.
RealmID string
// A map from Matrix room ID to Github "owner/repo"-style repositories.
Rooms map[string]struct {
Rooms map[id.RoomID]struct {
// A map of "owner/repo"-style repositories to the events to listen for.
Repos map[string]struct { // owner/repo => { events: ["push","issue","pull_request"] }
// The webhook events to listen for. Currently supported:
@ -79,7 +82,7 @@ type WebhookService struct {
//
// If the "owner/repo" string doesn't exist in this Service config, then the webhook will be deleted from
// Github.
func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
evType, repo, msg, err := webhook.OnReceiveRequest(req, s.SecretToken)
if err != nil {
w.WriteHeader(err.Code)
@ -108,7 +111,7 @@ func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Reque
"message": msg,
"room_id": roomID,
}).Print("Sending notification to room")
if _, e := cli.SendMessageEvent(roomID, "m.room.message", msg); e != nil {
if _, e := cli.SendMessageEvent(roomID, event.EventMessage, msg); e != nil {
logger.WithError(e).WithField("room_id", roomID).Print(
"Failed to send notification to room.")
}
@ -143,7 +146,7 @@ func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Reque
//
// Hooks can get out of sync if a user manually deletes a hook in the Github UI. In this case, toggling the repo configuration will
// force NEB to recreate the hook.
func (s *WebhookService) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *WebhookService) Register(oldService types.Service, client *mautrix.Client) error {
if s.RealmID == "" || s.ClientUserID == "" {
return fmt.Errorf("RealmID and ClientUserID is required")
}
@ -249,9 +252,9 @@ func (s *WebhookService) PostRegister(oldService types.Service) {
}
}
func (s *WebhookService) joinWebhookRooms(client *gomatrix.Client) error {
func (s *WebhookService) joinWebhookRooms(client *mautrix.Client) error {
for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID, "", nil); err != nil {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
// TODO: Leave the rooms we successfully joined?
return err
}
@ -300,7 +303,7 @@ func (s *WebhookService) createHook(cli *gogithub.Client, ownerRepo string) erro
cfg["secret"] = s.SecretToken
}
events := []string{"push", "pull_request", "issues", "issue_comment", "pull_request_review_comment"}
_, res, err := cli.Repositories.CreateHook(owner, repo, &gogithub.Hook{
_, res, err := cli.Repositories.CreateHook(context.Background(), owner, repo, &gogithub.Hook{
Name: &name,
Config: cfg,
Events: events,
@ -338,7 +341,7 @@ func (s *WebhookService) deleteHook(owner, repo string) error {
// Get a list of webhooks for this owner/repo and find the one which has the
// same endpoint URL which is what github uses to determine equivalence.
hooks, _, err := cli.Repositories.ListHooks(owner, repo, nil)
hooks, _, err := cli.Repositories.ListHooks(context.Background(), owner, repo, nil)
if err != nil {
return err
}
@ -362,7 +365,7 @@ func (s *WebhookService) deleteHook(owner, repo string) error {
return fmt.Errorf("Failed to find hook with endpoint: %s", s.webhookEndpointURL)
}
_, err = cli.Repositories.DeleteHook(owner, repo, *hook.ID)
_, err = cli.Repositories.DeleteHook(context.Background(), owner, repo, *hook.ID)
return err
}
@ -427,7 +430,7 @@ func difference(a, b []string) (onlyA, onlyB []string) {
}
}
func (s *WebhookService) githubClientFor(userID string, allowUnauth bool) *gogithub.Client {
func (s *WebhookService) githubClientFor(userID id.UserID, allowUnauth bool) *gogithub.Client {
token, err := getTokenForUser(s.RealmID, userID)
if err != nil {
log.WithFields(log.Fields{
@ -462,7 +465,7 @@ func (s *WebhookService) loadRealm() (types.AuthRealm, error) {
}
func init() {
types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
types.RegisterService(func(serviceID string, serviceUserID id.UserID, webhookEndpointURL string) types.Service {
return &WebhookService{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, WebhookServiceType),
webhookEndpointURL: webhookEndpointURL,

9
services/github/github_webhook_test.go

@ -13,7 +13,8 @@ import (
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
)
var roomID = "!testroom:id"
@ -22,13 +23,13 @@ func TestGithubWebhook(t *testing.T) {
database.SetServiceDB(&database.NopStorage{})
// Intercept message sending to Matrix and mock responses
msgs := []gomatrix.TextMessage{}
msgs := []mevt.MessageEventContent{}
matrixTrans := struct{ testutils.MockTransport }{}
matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if !strings.Contains(req.URL.String(), "/send/m.room.message") {
return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String())
}
var msg gomatrix.TextMessage
var msg mevt.MessageEventContent
if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
return nil, fmt.Errorf("Failed to decode request JSON: %s", err)
}
@ -38,7 +39,7 @@ func TestGithubWebhook(t *testing.T) {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)),
}, nil
}
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@ghwebhook:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@ghwebhook:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans}
// create the service

7
services/github/webhook/webhook.go

@ -12,16 +12,17 @@ import (
"strings"
"github.com/google/go-github/github"
"github.com/matrix-org/gomatrix"
"github.com/matrix-org/go-neb/services/utils"
"github.com/matrix-org/util"
log "github.com/sirupsen/logrus"
mevt "maunium.net/go/mautrix/event"
)
// OnReceiveRequest processes incoming github webhook requests and returns a
// matrix message to send, along with parsed repo information.
// The secretToken, if supplied, will be used to verify the request is from
// Github. If it isn't, an error is returned.
func OnReceiveRequest(r *http.Request, secretToken string) (string, *github.Repository, *gomatrix.HTMLMessage, *util.JSONResponse) {
func OnReceiveRequest(r *http.Request, secretToken string) (string, *github.Repository, *mevt.MessageEventContent, *util.JSONResponse) {
// Verify the HMAC signature if NEB was configured with a secret token
eventType := r.Header.Get("X-GitHub-Event")
signatureSHA1 := r.Header.Get("X-Hub-Signature")
@ -72,7 +73,7 @@ func OnReceiveRequest(r *http.Request, secretToken string) (string, *github.Repo
return "", nil, nil, &resErr
}
msg := gomatrix.GetHTMLMessage("m.notice", htmlStr)
msg := utils.StrippedHTMLMessage(mevt.MsgNotice, htmlStr)
return refinedType, repo, &msg, nil
}

21
services/utils/utils.go

@ -0,0 +1,21 @@
package utils
import (
"html"
"regexp"
mevt "maunium.net/go/mautrix/event"
)
var htmlRegex = regexp.MustCompile("<[^<]+?>")
// StrippedHTMLMessage returns a MessageEventContent with the body set to a stripped version of the provided HTML,
// in addition to the provided HTML.
func StrippedHTMLMessage(msgtype mevt.MessageType, htmlText string) mevt.MessageEventContent {
return mevt.MessageEventContent{
Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
MsgType: msgtype,
Format: mevt.FormatHTML,
FormattedBody: htmlText,
}
}

19
services/utils/utils_test.go

@ -0,0 +1,19 @@
package utils
import (
"testing"
mevt "maunium.net/go/mautrix/event"
)
func TestHTMLStrip(t *testing.T) {
msg := `before &lt;<hello a="b"><inside />during</hello>&gt; after`
stripped := StrippedHTMLMessage(mevt.MsgNotice, msg)
if stripped.MsgType != mevt.MsgNotice {
t.Fatalf("Expected MsgType %v, got %v", mevt.MsgNotice, stripped.MsgType)
}
expected := "before <during> after"
if stripped.Body != expected {
t.Fatalf(`Expected Body "%v", got "%v"`, expected, stripped.Body)
}
}
Loading…
Cancel
Save