From 69d063f0ec1b36e058ba00ce9e4115d7308c7301 Mon Sep 17 00:00:00 2001 From: Nikos Filippakis Date: Fri, 5 Jun 2020 21:30:37 +0200 Subject: [PATCH] Convert and re-enable Github realm and service, and update go-github version Signed-off-by: Nikos Filippakis --- go.mod | 2 +- goneb.go | 6 +- realms/github/github.go | 14 +- services/github/github.go | 215 ++++++++++++++++--------- services/github/github_webhook.go | 29 ++-- services/github/github_webhook_test.go | 9 +- services/github/webhook/webhook.go | 7 +- services/utils/utils.go | 21 +++ services/utils/utils_test.go | 19 +++ 9 files changed, 214 insertions(+), 108 deletions(-) create mode 100644 services/utils/utils.go create mode 100644 services/utils/utils_test.go diff --git a/go.mod b/go.mod index a8e01f9..24d8f26 100644 --- a/go.mod +++ b/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 diff --git a/goneb.go b/goneb.go index 5eb235b..3c4cabc 100644 --- a/goneb.go +++ b/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" diff --git a/realms/github/github.go b/realms/github/github.go index ab28a71..70eebd6 100644 --- a/realms/github/github.go +++ b/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, diff --git a/services/github/github.go b/services/github/github.go index 7cbd413..702eda6 100644 --- a/services/github/github.go +++ b/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("") - 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), } diff --git a/services/github/github_webhook.go b/services/github/github_webhook.go index f8bcea9..4c54d2e 100644 --- a/services/github/github_webhook.go +++ b/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, diff --git a/services/github/github_webhook_test.go b/services/github/github_webhook_test.go index b73c083..2d9d3d5 100644 --- a/services/github/github_webhook_test.go +++ b/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 diff --git a/services/github/webhook/webhook.go b/services/github/webhook/webhook.go index bfa8720..d639ab7 100644 --- a/services/github/webhook/webhook.go +++ b/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 } diff --git a/services/utils/utils.go b/services/utils/utils.go new file mode 100644 index 0000000..4a991b2 --- /dev/null +++ b/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, + } +} diff --git a/services/utils/utils_test.go b/services/utils/utils_test.go new file mode 100644 index 0000000..0c418a1 --- /dev/null +++ b/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 <during> after` + stripped := StrippedHTMLMessage(mevt.MsgNotice, msg) + if stripped.MsgType != mevt.MsgNotice { + t.Fatalf("Expected MsgType %v, got %v", mevt.MsgNotice, stripped.MsgType) + } + expected := "before after" + if stripped.Body != expected { + t.Fatalf(`Expected Body "%v", got "%v"`, expected, stripped.Body) + } +}