Browse Source

[WIP] Switch Gomatrix for Mautrix (#322)

* Switch core functionality to Mautrix

Signed-off-by: Nikos Filippakis <me@nfil.dev>

* Convert and re-enable rssbot

Signed-off-by: Nikos Filippakis <me@nfil.dev>

* Convert and re-enable giphy service

Signed-off-by: Nikos Filippakis <me@nfil.dev>

* Convert and re-enable wikipedia, imgur and guggy services

Signed-off-by: Nikos Filippakis <me@nfil.dev>

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

Signed-off-by: Nikos Filippakis <me@nfil.dev>

* Convert and add the rest of the services

Re-enables the services: alertmanager, google, jira, slackapi, travisci

Signed-off-by: Nikos Filippakis <me@nfil.dev>
pull/327/head
Nikos Filippakis 5 years ago
committed by GitHub
parent
commit
6409b00205
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .travis.yml
  2. 10
      Dockerfile
  3. 4
      README.md
  4. 10
      api/api.go
  5. 5
      api/handlers/auth.go
  6. 4
      api/handlers/service.go
  7. 81
      clients/clients.go
  8. 30
      clients/clients_test.go
  9. 18
      database/db.go
  10. 29
      database/interface.go
  11. 27
      database/schema.go
  12. 14
      go.mod
  13. 74
      go.sum
  14. 3
      goneb.go
  15. 11
      matrix/matrix.go
  16. 14
      realms/github/github.go
  17. 15
      realms/jira/jira.go
  18. 33
      services/alertmanager/alertmanager.go
  19. 18
      services/alertmanager/alertmanager_test.go
  20. 15
      services/echo/echo.go
  21. 29
      services/giphy/giphy.go
  22. 215
      services/github/github.go
  23. 29
      services/github/github_webhook.go
  24. 9
      services/github/github_webhook_test.go
  25. 7
      services/github/webhook/webhook.go
  26. 48
      services/google/google.go
  27. 4
      services/google/google_test.go
  28. 30
      services/guggy/guggy.go
  29. 4
      services/guggy/guggy_test.go
  30. 50
      services/imgur/imgur.go
  31. 4
      services/imgur/imgur_test.go
  32. 43
      services/jira/jira.go
  33. 7
      services/jira/webhook/webhook.go
  34. 28
      services/rssbot/rssbot.go
  35. 10
      services/rssbot/rssbot_test.go
  36. 4
      services/slackapi/message.go
  37. 20
      services/slackapi/slackapi.go
  38. 20
      services/travisci/travisci.go
  39. 13
      services/travisci/travisci_test.go
  40. 21
      services/utils/utils.go
  41. 19
      services/utils/utils_test.go
  42. 28
      services/wikipedia/wikipedia.go
  43. 4
      services/wikipedia/wikipedia_test.go
  44. 6
      testutil_test.go
  45. 6
      types/actions.go
  46. 8
      types/auth.go
  47. 41
      types/service.go

4
.travis.yml

@ -1,7 +1,11 @@
os: linux
dist: bionic
language: go language: go
go: go:
- 1.14 - 1.14
install: install:
- sudo apt-get update
- sudo apt-get -y install libolm2 libolm-dev
- go get golang.org/x/lint/golint - go get golang.org/x/lint/golint
- go get github.com/fzipp/gocyclo - go get github.com/fzipp/gocyclo

10
Dockerfile

@ -1,7 +1,11 @@
# Build go-neb # Build go-neb
FROM golang:1.14-alpine as builder FROM golang:1.14-alpine as builder
RUN apk add --no-cache -t build-deps git gcc musl-dev go
RUN apk add --no-cache -t build-deps git gcc musl-dev go make g++
RUN git clone https://gitlab.matrix.org/matrix-org/olm.git /tmp/libolm \
&& cd /tmp/libolm \
&& make install
COPY . /tmp/go-neb COPY . /tmp/go-neb
WORKDIR /tmp/go-neb WORKDIR /tmp/go-neb
@ -22,7 +26,11 @@ ENV BIND_ADDRESS=:4050 \
GID=1337 GID=1337
COPY --from=builder /tmp/go-neb/go-neb /usr/local/bin/go-neb COPY --from=builder /tmp/go-neb/go-neb /usr/local/bin/go-neb
# Copy libolm.so
COPY --from=builder /usr/local/lib/* /usr/local/lib/
RUN apk add --no-cache \ RUN apk add --no-cache \
libstdc++ \
ca-certificates \ ca-certificates \
su-exec \ su-exec \
s6 s6

4
README.md

@ -169,6 +169,10 @@ Before submitting pull requests, please read the [Matrix.org contribution guidel
# Developing # Developing
This project depends on `libolm` for the end-to-end encryption. Therefore,
you need to install `libolm2` and `libolm-dev` on Ubuntu / `libolm-devel` on
CentOS to be able to build and run it.
There's a bunch more tools this project uses when developing in order to do There's a bunch more tools this project uses when developing in order to do
things like linting. Some of them are bundled with go (fmt and vet) but some things like linting. Some of them are bundled with go (fmt and vet) but some
are not. You should install the ones which are not: are not. You should install the ones which are not:

10
api/api.go

@ -12,6 +12,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net/url" "net/url"
"maunium.net/go/mautrix/id"
) )
// ConfigureAuthRealmRequest is a request to /configureAuthRealm // ConfigureAuthRealmRequest is a request to /configureAuthRealm
@ -32,7 +34,7 @@ type RequestAuthSessionRequest struct {
RealmID string RealmID string
// The Matrix user ID requesting the auth session. If the auth is successful, // The Matrix user ID requesting the auth session. If the auth is successful,
// this user ID will be associated with the third-party credentials obtained. // this user ID will be associated with the third-party credentials obtained.
UserID string
UserID id.UserID
// AuthRealm specific config information. See the docs for the auth realm you're interested in. // AuthRealm specific config information. See the docs for the auth realm you're interested in.
Config json.RawMessage Config json.RawMessage
} }
@ -47,7 +49,7 @@ type ConfigureServiceRequest struct {
Type string Type string
// The user ID of the configured client that this service will use to communicate with Matrix. // The user ID of the configured client that this service will use to communicate with Matrix.
// The user MUST already be configured. // The user MUST already be configured.
UserID string
UserID id.UserID
// Service-specific config information. See the docs for the service you're interested in. // Service-specific config information. See the docs for the service you're interested in.
Config json.RawMessage Config json.RawMessage
} }
@ -56,7 +58,7 @@ type ConfigureServiceRequest struct {
// Go-NEB can drive it. It forms the HTTP body to /configureClient requests. // Go-NEB can drive it. It forms the HTTP body to /configureClient requests.
type ClientConfig struct { type ClientConfig struct {
// The matrix User ID to connect with. E.g. @alice:matrix.org // The matrix User ID to connect with. E.g. @alice:matrix.org
UserID string
UserID id.UserID
// A URL with the host and port of the matrix server. E.g. https://matrix.org:8448 // A URL with the host and port of the matrix server. E.g. https://matrix.org:8448
HomeserverURL string HomeserverURL string
// The matrix access token to authenticate the requests with. // The matrix access token to authenticate the requests with.
@ -81,7 +83,7 @@ type ClientConfig struct {
type Session struct { type Session struct {
SessionID string SessionID string
RealmID string RealmID string
UserID string
UserID id.UserID
Config json.RawMessage Config json.RawMessage
} }

5
api/handlers/auth.go

@ -13,6 +13,7 @@ import (
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/util" "github.com/matrix-org/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix/id"
) )
// RequestAuthSession represents an HTTP handler capable of processing /admin/requestAuthSession requests. // RequestAuthSession represents an HTTP handler capable of processing /admin/requestAuthSession requests.
@ -104,7 +105,7 @@ func (h *RemoveAuthSession) OnIncomingRequest(req *http.Request) util.JSONRespon
} }
var body struct { var body struct {
RealmID string RealmID string
UserID string
UserID id.UserID
} }
if err := json.NewDecoder(req.Body).Decode(&body); err != nil { if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
return util.MessageResponse(400, "Error parsing request JSON") return util.MessageResponse(400, "Error parsing request JSON")
@ -276,7 +277,7 @@ func (h *GetSession) OnIncomingRequest(req *http.Request) util.JSONResponse {
} }
var body struct { var body struct {
RealmID string RealmID string
UserID string
UserID id.UserID
} }
if err := json.NewDecoder(req.Body).Decode(&body); err != nil { if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
return util.MessageResponse(400, "Error parsing request JSON") return util.MessageResponse(400, "Error parsing request JSON")

4
api/handlers/service.go

@ -14,9 +14,9 @@ import (
"github.com/matrix-org/go-neb/metrics" "github.com/matrix-org/go-neb/metrics"
"github.com/matrix-org/go-neb/polling" "github.com/matrix-org/go-neb/polling"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"github.com/matrix-org/util" "github.com/matrix-org/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
) )
// ConfigureService represents an HTTP handler which can process /admin/configureService requests. // ConfigureService represents an HTTP handler which can process /admin/configureService requests.
@ -225,7 +225,7 @@ func (h *GetService) OnIncomingRequest(req *http.Request) util.JSONResponse {
} }
} }
func checkClientForService(service types.Service, client *gomatrix.Client) error {
func checkClientForService(service types.Service, client *mautrix.Client) error {
// If there are any commands or expansions for this Service then the service user ID // If there are any commands or expansions for this Service then the service user ID
// MUST be a syncing client or else the Service will never get the incoming command/expansion! // MUST be a syncing client or else the Service will never get the incoming command/expansion!
cmds := service.Commands(client) cmds := service.Commands(client)

81
clients/clients.go

@ -13,9 +13,11 @@ import (
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/metrics" "github.com/matrix-org/go-neb/metrics"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// A Clients is a collection of clients used for bot services. // A Clients is a collection of clients used for bot services.
@ -24,7 +26,7 @@ type Clients struct {
httpClient *http.Client httpClient *http.Client
dbMutex sync.Mutex dbMutex sync.Mutex
mapMutex sync.Mutex mapMutex sync.Mutex
clients map[string]clientEntry
clients map[id.UserID]clientEntry
} }
// New makes a new collection of matrix clients // New makes a new collection of matrix clients
@ -32,13 +34,13 @@ func New(db database.Storer, cli *http.Client) *Clients {
clients := &Clients{ clients := &Clients{
db: db, db: db,
httpClient: cli, httpClient: cli,
clients: make(map[string]clientEntry), // user_id => clientEntry
clients: make(map[id.UserID]clientEntry), // user_id => clientEntry
} }
return clients return clients
} }
// Client gets a client for the userID // Client gets a client for the userID
func (c *Clients) Client(userID string) (*gomatrix.Client, error) {
func (c *Clients) Client(userID id.UserID) (*mautrix.Client, error) {
entry := c.getClient(userID) entry := c.getClient(userID)
if entry.client != nil { if entry.client != nil {
return entry.client, nil return entry.client, nil
@ -71,10 +73,10 @@ func (c *Clients) Start() error {
type clientEntry struct { type clientEntry struct {
config api.ClientConfig config api.ClientConfig
client *gomatrix.Client
client *mautrix.Client
} }
func (c *Clients) getClient(userID string) clientEntry {
func (c *Clients) getClient(userID id.UserID) clientEntry {
c.mapMutex.Lock() c.mapMutex.Lock()
defer c.mapMutex.Unlock() defer c.mapMutex.Unlock()
return c.clients[userID] return c.clients[userID]
@ -86,7 +88,7 @@ func (c *Clients) setClient(client clientEntry) {
c.clients[client.config.UserID] = client c.clients[client.config.UserID] = client
} }
func (c *Clients) loadClientFromDB(userID string) (entry clientEntry, err error) {
func (c *Clients) loadClientFromDB(userID id.UserID) (entry clientEntry, err error) {
c.dbMutex.Lock() c.dbMutex.Lock()
defer c.dbMutex.Unlock() defer c.dbMutex.Unlock()
@ -153,7 +155,7 @@ func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry,
return return
} }
func (c *Clients) onMessageEvent(client *gomatrix.Client, event *gomatrix.Event) {
func (c *Clients) onMessageEvent(client *mautrix.Client, event *mevt.Event) {
services, err := c.db.LoadServicesForUser(client.UserID) services, err := c.db.LoadServicesForUser(client.UserID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -163,13 +165,19 @@ func (c *Clients) onMessageEvent(client *gomatrix.Client, event *gomatrix.Event)
}).Warn("Error loading services") }).Warn("Error loading services")
} }
body, ok := event.Body()
if !ok || body == "" {
if err := event.Content.ParseRaw(mevt.EventMessage); err != nil {
return
}
message := event.Content.AsMessage()
body := message.Body
if body == "" {
return return
} }
// filter m.notice to prevent loops // filter m.notice to prevent loops
if msgtype, ok := event.MessageType(); !ok || msgtype == "m.notice" {
if message.MsgType == mevt.MsgNotice {
return return
} }
@ -198,7 +206,7 @@ func (c *Clients) onMessageEvent(client *gomatrix.Client, event *gomatrix.Event)
} }
for _, content := range responses { for _, content := range responses {
if _, err := client.SendMessageEvent(event.RoomID, "m.room.message", content); err != nil {
if _, err := client.SendMessageEvent(event.RoomID, mevt.EventMessage, content); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
"room_id": event.RoomID, "room_id": event.RoomID,
@ -213,7 +221,7 @@ func (c *Clients) onMessageEvent(client *gomatrix.Client, event *gomatrix.Event)
// the matching command with the longest path. Returns the JSON encodable // the matching command with the longest path. Returns the JSON encodable
// content of a single matrix message event to use as a response or nil if no // content of a single matrix message event to use as a response or nil if no
// response is appropriate. // response is appropriate.
func runCommandForService(cmds []types.Command, event *gomatrix.Event, arguments []string) interface{} {
func runCommandForService(cmds []types.Command, event *mevt.Event, arguments []string) interface{} {
var bestMatch *types.Command var bestMatch *types.Command
for i, command := range cmds { for i, command := range cmds {
matches := command.Matches(arguments) matches := command.Matches(arguments)
@ -245,7 +253,10 @@ func runCommandForService(cmds []types.Command, event *gomatrix.Event, arguments
}).Warn("Command returned both error and content.") }).Warn("Command returned both error and content.")
} }
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusFailure) metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusFailure)
content = gomatrix.TextMessage{"m.notice", err.Error()}
content = mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: err.Error(),
}
} else { } else {
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusSuccess) metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusSuccess)
} }
@ -254,7 +265,7 @@ func runCommandForService(cmds []types.Command, event *gomatrix.Event, arguments
} }
// run the expansions for a matrix event. // run the expansions for a matrix event.
func runExpansionsForService(expans []types.Expansion, event *gomatrix.Event, body string) []interface{} {
func runExpansionsForService(expans []types.Expansion, event *mevt.Event, body string) []interface{} {
var responses []interface{} var responses []interface{}
for _, expansion := range expans { for _, expansion := range expans {
@ -275,10 +286,13 @@ func runExpansionsForService(expans []types.Expansion, event *gomatrix.Event, bo
return responses return responses
} }
func (c *Clients) onBotOptionsEvent(client *gomatrix.Client, event *gomatrix.Event) {
func (c *Clients) onBotOptionsEvent(client *mautrix.Client, event *mevt.Event) {
// see if these options are for us. The state key is the user ID with a leading _ // see if these options are for us. The state key is the user ID with a leading _
// to get around restrictions in the HS about having user IDs as state keys. // to get around restrictions in the HS about having user IDs as state keys.
targetUserID := strings.TrimPrefix(*event.StateKey, "_")
if event.StateKey == nil {
return
}
targetUserID := id.UserID(strings.TrimPrefix(*event.StateKey, "_"))
if targetUserID != client.UserID { if targetUserID != client.UserID {
return return
} }
@ -287,7 +301,7 @@ func (c *Clients) onBotOptionsEvent(client *gomatrix.Client, event *gomatrix.Eve
UserID: client.UserID, UserID: client.UserID,
RoomID: event.RoomID, RoomID: event.RoomID,
SetByUserID: event.Sender, SetByUserID: event.Sender,
Options: event.Content,
Options: event.Content.Raw,
} }
if _, err := c.db.StoreBotOptions(opts); err != nil { if _, err := c.db.StoreBotOptions(opts); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -299,15 +313,14 @@ func (c *Clients) onBotOptionsEvent(client *gomatrix.Client, event *gomatrix.Eve
} }
} }
func (c *Clients) onRoomMemberEvent(client *gomatrix.Client, event *gomatrix.Event) {
if *event.StateKey != client.UserID {
return // not our member event
}
m := event.Content["membership"]
membership, ok := m.(string)
if !ok {
func (c *Clients) onRoomMemberEvent(client *mautrix.Client, event *mevt.Event) {
if err := event.Content.ParseRaw(mevt.StateMember); err != nil {
return return
} }
if event.StateKey == nil || *event.StateKey != client.UserID.String() {
return // not our member event
}
membership := event.Content.AsMember().Membership
if membership == "invite" { if membership == "invite" {
logger := log.WithFields(log.Fields{ logger := log.WithFields(log.Fields{
"room_id": event.RoomID, "room_id": event.RoomID,
@ -317,10 +330,10 @@ func (c *Clients) onRoomMemberEvent(client *gomatrix.Client, event *gomatrix.Eve
logger.Print("Accepting invite from user") logger.Print("Accepting invite from user")
content := struct { content := struct {
Inviter string `json:"inviter"`
Inviter id.UserID `json:"inviter"`
}{event.Sender} }{event.Sender}
if _, err := client.JoinRoom(event.RoomID, "", content); err != nil {
if _, err := client.JoinRoom(event.RoomID.String(), "", content); err != nil {
logger.WithError(err).Print("Failed to join room") logger.WithError(err).Print("Failed to join room")
} else { } else {
logger.Print("Joined room") logger.Print("Joined room")
@ -328,15 +341,15 @@ func (c *Clients) onRoomMemberEvent(client *gomatrix.Client, event *gomatrix.Eve
} }
} }
func (c *Clients) newClient(config api.ClientConfig) (*gomatrix.Client, error) {
client, err := gomatrix.NewClient(config.HomeserverURL, config.UserID, config.AccessToken)
func (c *Clients) newClient(config api.ClientConfig) (*mautrix.Client, error) {
client, err := mautrix.NewClient(config.HomeserverURL, config.UserID, config.AccessToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client.Client = c.httpClient client.Client = c.httpClient
syncer := client.Syncer.(*gomatrix.DefaultSyncer)
syncer := client.Syncer.(*mautrix.DefaultSyncer)
nebStore := &matrix.NEBStore{ nebStore := &matrix.NEBStore{
InMemoryStore: *gomatrix.NewInMemoryStore(),
InMemoryStore: *mautrix.NewInMemoryStore(),
Database: c.db, Database: c.db,
ClientConfig: config, ClientConfig: config,
} }
@ -346,16 +359,16 @@ func (c *Clients) newClient(config api.ClientConfig) (*gomatrix.Client, error) {
// TODO: Check that the access token is valid for the userID by peforming // TODO: Check that the access token is valid for the userID by peforming
// a request against the server. // a request against the server.
syncer.OnEventType("m.room.message", func(event *gomatrix.Event) {
syncer.OnEventType(mevt.EventMessage, func(event *mevt.Event) {
c.onMessageEvent(client, event) c.onMessageEvent(client, event)
}) })
syncer.OnEventType("m.room.bot.options", func(event *gomatrix.Event) {
syncer.OnEventType(mevt.Type{Type: "m.room.bot.options", Class: mevt.UnknownEventType}, func(event *mevt.Event) {
c.onBotOptionsEvent(client, event) c.onBotOptionsEvent(client, event)
}) })
if config.AutoJoinRooms { if config.AutoJoinRooms {
syncer.OnEventType("m.room.member", func(event *gomatrix.Event) {
syncer.OnEventType(mevt.StateMember, func(event *mevt.Event) {
c.onRoomMemberEvent(client, event) c.onRoomMemberEvent(client, event)
}) })
} }

30
clients/clients_test.go

@ -8,7 +8,9 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
var commandParseTests = []struct { var commandParseTests = []struct {
@ -28,7 +30,7 @@ type MockService struct {
commands []types.Command commands []types.Command
} }
func (s *MockService) Commands(cli *gomatrix.Client) []types.Command {
func (s *MockService) Commands(cli *mautrix.Client) []types.Command {
return s.commands return s.commands
} }
@ -37,7 +39,7 @@ type MockStore struct {
service types.Service service types.Service
} }
func (d *MockStore) LoadServicesForUser(userID string) ([]types.Service, error) {
func (d *MockStore) LoadServicesForUser(userID id.UserID) ([]types.Service, error) {
return []types.Service{d.service}, nil return []types.Service{d.service}, nil
} }
@ -54,7 +56,7 @@ func TestCommandParsing(t *testing.T) {
cmds := []types.Command{ cmds := []types.Command{
types.Command{ types.Command{
Path: []string{"test"}, Path: []string{"test"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
executedCmdArgs = args executedCmdArgs = args
return nil, nil return nil, nil
}, },
@ -72,19 +74,25 @@ func TestCommandParsing(t *testing.T) {
Transport: trans, Transport: trans,
} }
clients := New(&store, cli) clients := New(&store, cli)
mxCli, _ := gomatrix.NewClient("https://someplace.somewhere", "@service:user", "token")
mxCli, _ := mautrix.NewClient("https://someplace.somewhere", "@service:user", "token")
mxCli.Client = cli mxCli.Client = cli
for _, input := range commandParseTests { for _, input := range commandParseTests {
executedCmdArgs = []string{} executedCmdArgs = []string{}
event := gomatrix.Event{
Type: "m.room.message",
Sender: "@someone:somewhere",
RoomID: "!foo:bar",
Content: map[string]interface{}{
content := mevt.Content{Raw: map[string]interface{}{
"body": input.body, "body": input.body,
"msgtype": "m.text", "msgtype": "m.text",
},
}}
if veryRaw, err := content.MarshalJSON(); err != nil {
t.Errorf("Error marshalling JSON: %s", err)
} else {
content.VeryRaw = veryRaw
}
event := mevt.Event{
Type: mevt.EventMessage,
Sender: "@someone:somewhere",
RoomID: "!foo:bar",
Content: content,
} }
clients.onMessageEvent(mxCli, &event) clients.onMessageEvent(mxCli, &event)
if !reflect.DeepEqual(executedCmdArgs, input.expectArgs) { if !reflect.DeepEqual(executedCmdArgs, input.expectArgs) {

18
database/db.go

@ -4,9 +4,11 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
"github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"time"
"maunium.net/go/mautrix/id"
) )
// A ServiceDB stores the configuration for the services // A ServiceDB stores the configuration for the services
@ -75,7 +77,7 @@ func (d *ServiceDB) LoadMatrixClientConfigs() (configs []api.ClientConfig, err e
// LoadMatrixClientConfig loads a Matrix client config from the database. // LoadMatrixClientConfig loads a Matrix client config from the database.
// Returns sql.ErrNoRows if the client isn't in the database. // Returns sql.ErrNoRows if the client isn't in the database.
func (d *ServiceDB) LoadMatrixClientConfig(userID string) (config api.ClientConfig, err error) {
func (d *ServiceDB) LoadMatrixClientConfig(userID id.UserID) (config api.ClientConfig, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
config, err = selectMatrixClientConfigTxn(txn, userID) config, err = selectMatrixClientConfigTxn(txn, userID)
return err return err
@ -84,7 +86,7 @@ func (d *ServiceDB) LoadMatrixClientConfig(userID string) (config api.ClientConf
} }
// UpdateNextBatch updates the next_batch token for the given user. // UpdateNextBatch updates the next_batch token for the given user.
func (d *ServiceDB) UpdateNextBatch(userID, nextBatch string) (err error) {
func (d *ServiceDB) UpdateNextBatch(userID id.UserID, nextBatch string) (err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
return updateNextBatchTxn(txn, userID, nextBatch) return updateNextBatchTxn(txn, userID, nextBatch)
}) })
@ -92,7 +94,7 @@ func (d *ServiceDB) UpdateNextBatch(userID, nextBatch string) (err error) {
} }
// LoadNextBatch loads the next_batch token for the given user. // LoadNextBatch loads the next_batch token for the given user.
func (d *ServiceDB) LoadNextBatch(userID string) (nextBatch string, err error) {
func (d *ServiceDB) LoadNextBatch(userID id.UserID) (nextBatch string, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
nextBatch, err = selectNextBatchTxn(txn, userID) nextBatch, err = selectNextBatchTxn(txn, userID)
return err return err
@ -120,7 +122,7 @@ func (d *ServiceDB) DeleteService(serviceID string) (err error) {
// LoadServicesForUser loads all the bot services configured for a given user. // LoadServicesForUser loads all the bot services configured for a given user.
// Returns an empty list if there aren't any services configured. // Returns an empty list if there aren't any services configured.
func (d *ServiceDB) LoadServicesForUser(serviceUserID string) (services []types.Service, err error) {
func (d *ServiceDB) LoadServicesForUser(serviceUserID id.UserID) (services []types.Service, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
services, err = selectServicesForUserTxn(txn, serviceUserID) services, err = selectServicesForUserTxn(txn, serviceUserID)
if err != nil { if err != nil {
@ -218,7 +220,7 @@ func (d *ServiceDB) StoreAuthSession(session types.AuthSession) (old types.AuthS
// RemoveAuthSession removes the auth session for the given user on the given realm. // RemoveAuthSession removes the auth session for the given user on the given realm.
// No error is returned if the session did not exist in the first place. // No error is returned if the session did not exist in the first place.
func (d *ServiceDB) RemoveAuthSession(realmID, userID string) error {
func (d *ServiceDB) RemoveAuthSession(realmID string, userID id.UserID) error {
return runTransaction(d.db, func(txn *sql.Tx) error { return runTransaction(d.db, func(txn *sql.Tx) error {
return deleteAuthSessionTxn(txn, realmID, userID) return deleteAuthSessionTxn(txn, realmID, userID)
}) })
@ -227,7 +229,7 @@ func (d *ServiceDB) RemoveAuthSession(realmID, userID string) error {
// LoadAuthSessionByUser loads an AuthSession from the database based on the given // LoadAuthSessionByUser loads an AuthSession from the database based on the given
// realm and user ID. // realm and user ID.
// Returns sql.ErrNoRows if the session isn't in the database. // Returns sql.ErrNoRows if the session isn't in the database.
func (d *ServiceDB) LoadAuthSessionByUser(realmID, userID string) (session types.AuthSession, err error) {
func (d *ServiceDB) LoadAuthSessionByUser(realmID string, userID id.UserID) (session types.AuthSession, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
session, err = selectAuthSessionByUserTxn(txn, realmID, userID) session, err = selectAuthSessionByUserTxn(txn, realmID, userID)
return err return err
@ -248,7 +250,7 @@ func (d *ServiceDB) LoadAuthSessionByID(realmID, sessionID string) (session type
// LoadBotOptions loads bot options from the database. // LoadBotOptions loads bot options from the database.
// Returns sql.ErrNoRows if the bot options isn't in the database. // Returns sql.ErrNoRows if the bot options isn't in the database.
func (d *ServiceDB) LoadBotOptions(userID, roomID string) (opts types.BotOptions, err error) {
func (d *ServiceDB) LoadBotOptions(userID id.UserID, roomID id.RoomID) (opts types.BotOptions, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error { err = runTransaction(d.db, func(txn *sql.Tx) error {
opts, err = selectBotOptionsTxn(txn, userID, roomID) opts, err = selectBotOptionsTxn(txn, userID, roomID)
return err return err

29
database/interface.go

@ -3,20 +3,21 @@ package database
import ( import (
"github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"maunium.net/go/mautrix/id"
) )
// Storer is the interface which needs to be conformed to in order to persist Go-NEB data // Storer is the interface which needs to be conformed to in order to persist Go-NEB data
type Storer interface { type Storer interface {
StoreMatrixClientConfig(config api.ClientConfig) (oldConfig api.ClientConfig, err error) StoreMatrixClientConfig(config api.ClientConfig) (oldConfig api.ClientConfig, err error)
LoadMatrixClientConfigs() (configs []api.ClientConfig, err error) LoadMatrixClientConfigs() (configs []api.ClientConfig, err error)
LoadMatrixClientConfig(userID string) (config api.ClientConfig, err error)
LoadMatrixClientConfig(userID id.UserID) (config api.ClientConfig, err error)
UpdateNextBatch(userID, nextBatch string) (err error)
LoadNextBatch(userID string) (nextBatch string, err error)
UpdateNextBatch(userID id.UserID, nextBatch string) (err error)
LoadNextBatch(userID id.UserID) (nextBatch string, err error)
LoadService(serviceID string) (service types.Service, err error) LoadService(serviceID string) (service types.Service, err error)
DeleteService(serviceID string) (err error) DeleteService(serviceID string) (err error)
LoadServicesForUser(serviceUserID string) (services []types.Service, err error)
LoadServicesForUser(serviceUserID id.UserID) (services []types.Service, err error)
LoadServicesByType(serviceType string) (services []types.Service, err error) LoadServicesByType(serviceType string) (services []types.Service, err error)
StoreService(service types.Service) (oldService types.Service, err error) StoreService(service types.Service) (oldService types.Service, err error)
@ -25,11 +26,11 @@ type Storer interface {
StoreAuthRealm(realm types.AuthRealm) (old types.AuthRealm, err error) StoreAuthRealm(realm types.AuthRealm) (old types.AuthRealm, err error)
StoreAuthSession(session types.AuthSession) (old types.AuthSession, err error) StoreAuthSession(session types.AuthSession) (old types.AuthSession, err error)
LoadAuthSessionByUser(realmID, userID string) (session types.AuthSession, err error)
LoadAuthSessionByUser(realmID string, userID id.UserID) (session types.AuthSession, err error)
LoadAuthSessionByID(realmID, sessionID string) (session types.AuthSession, err error) LoadAuthSessionByID(realmID, sessionID string) (session types.AuthSession, err error)
RemoveAuthSession(realmID, userID string) error
RemoveAuthSession(realmID string, userID id.UserID) error
LoadBotOptions(userID, roomID string) (opts types.BotOptions, err error)
LoadBotOptions(userID id.UserID, roomID id.RoomID) (opts types.BotOptions, err error)
StoreBotOptions(opts types.BotOptions) (oldOpts types.BotOptions, err error) StoreBotOptions(opts types.BotOptions) (oldOpts types.BotOptions, err error)
InsertFromConfig(cfg *api.ConfigFile) error InsertFromConfig(cfg *api.ConfigFile) error
@ -50,17 +51,17 @@ func (s *NopStorage) LoadMatrixClientConfigs() (configs []api.ClientConfig, err
} }
// LoadMatrixClientConfig NOP // LoadMatrixClientConfig NOP
func (s *NopStorage) LoadMatrixClientConfig(userID string) (config api.ClientConfig, err error) {
func (s *NopStorage) LoadMatrixClientConfig(userID id.UserID) (config api.ClientConfig, err error) {
return return
} }
// UpdateNextBatch NOP // UpdateNextBatch NOP
func (s *NopStorage) UpdateNextBatch(userID, nextBatch string) (err error) {
func (s *NopStorage) UpdateNextBatch(userID id.UserID, nextBatch string) (err error) {
return return
} }
// LoadNextBatch NOP // LoadNextBatch NOP
func (s *NopStorage) LoadNextBatch(userID string) (nextBatch string, err error) {
func (s *NopStorage) LoadNextBatch(userID id.UserID) (nextBatch string, err error) {
return return
} }
@ -75,7 +76,7 @@ func (s *NopStorage) DeleteService(serviceID string) (err error) {
} }
// LoadServicesForUser NOP // LoadServicesForUser NOP
func (s *NopStorage) LoadServicesForUser(serviceUserID string) (services []types.Service, err error) {
func (s *NopStorage) LoadServicesForUser(serviceUserID id.UserID) (services []types.Service, err error) {
return return
} }
@ -110,7 +111,7 @@ func (s *NopStorage) StoreAuthSession(session types.AuthSession) (old types.Auth
} }
// LoadAuthSessionByUser NOP // LoadAuthSessionByUser NOP
func (s *NopStorage) LoadAuthSessionByUser(realmID, userID string) (session types.AuthSession, err error) {
func (s *NopStorage) LoadAuthSessionByUser(realmID string, userID id.UserID) (session types.AuthSession, err error) {
return return
} }
@ -120,12 +121,12 @@ func (s *NopStorage) LoadAuthSessionByID(realmID, sessionID string) (session typ
} }
// RemoveAuthSession NOP // RemoveAuthSession NOP
func (s *NopStorage) RemoveAuthSession(realmID, userID string) error {
func (s *NopStorage) RemoveAuthSession(realmID string, userID id.UserID) error {
return nil return nil
} }
// LoadBotOptions NOP // LoadBotOptions NOP
func (s *NopStorage) LoadBotOptions(userID, roomID string) (opts types.BotOptions, err error) {
func (s *NopStorage) LoadBotOptions(userID id.UserID, roomID id.RoomID) (opts types.BotOptions, err error) {
return return
} }

27
database/schema.go

@ -8,6 +8,7 @@ import (
"github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"maunium.net/go/mautrix/id"
) )
const schemaSQL = ` const schemaSQL = `
@ -66,7 +67,7 @@ const selectMatrixClientConfigSQL = `
SELECT client_json FROM matrix_clients WHERE user_id = $1 SELECT client_json FROM matrix_clients WHERE user_id = $1
` `
func selectMatrixClientConfigTxn(txn *sql.Tx, userID string) (config api.ClientConfig, err error) {
func selectMatrixClientConfigTxn(txn *sql.Tx, userID id.UserID) (config api.ClientConfig, err error) {
var configJSON []byte var configJSON []byte
err = txn.QueryRow(selectMatrixClientConfigSQL, userID).Scan(&configJSON) err = txn.QueryRow(selectMatrixClientConfigSQL, userID).Scan(&configJSON)
if err != nil { if err != nil {
@ -135,7 +136,7 @@ const updateNextBatchSQL = `
UPDATE matrix_clients SET next_batch = $1 WHERE user_id = $2 UPDATE matrix_clients SET next_batch = $1 WHERE user_id = $2
` `
func updateNextBatchTxn(txn *sql.Tx, userID, nextBatch string) error {
func updateNextBatchTxn(txn *sql.Tx, userID id.UserID, nextBatch string) error {
_, err := txn.Exec(updateNextBatchSQL, nextBatch, userID) _, err := txn.Exec(updateNextBatchSQL, nextBatch, userID)
return err return err
} }
@ -144,7 +145,7 @@ const selectNextBatchSQL = `
SELECT next_batch FROM matrix_clients WHERE user_id = $1 SELECT next_batch FROM matrix_clients WHERE user_id = $1
` `
func selectNextBatchTxn(txn *sql.Tx, userID string) (string, error) {
func selectNextBatchTxn(txn *sql.Tx, userID id.UserID) (string, error) {
var nextBatch string var nextBatch string
row := txn.QueryRow(selectNextBatchSQL, userID) row := txn.QueryRow(selectNextBatchSQL, userID)
if err := row.Scan(&nextBatch); err != nil { if err := row.Scan(&nextBatch); err != nil {
@ -160,7 +161,7 @@ SELECT service_type, service_user_id, service_json FROM services
func selectServiceTxn(txn *sql.Tx, serviceID string) (types.Service, error) { func selectServiceTxn(txn *sql.Tx, serviceID string) (types.Service, error) {
var serviceType string var serviceType string
var serviceUserID string
var serviceUserID id.UserID
var serviceJSON []byte var serviceJSON []byte
row := txn.QueryRow(selectServiceSQL, serviceID) row := txn.QueryRow(selectServiceSQL, serviceID)
if err := row.Scan(&serviceType, &serviceUserID, &serviceJSON); err != nil { if err := row.Scan(&serviceType, &serviceUserID, &serviceJSON); err != nil {
@ -210,7 +211,7 @@ const selectServicesForUserSQL = `
SELECT service_id, service_type, service_json FROM services WHERE service_user_id=$1 ORDER BY service_id SELECT service_id, service_type, service_json FROM services WHERE service_user_id=$1 ORDER BY service_id
` `
func selectServicesForUserTxn(txn *sql.Tx, userID string) (srvs []types.Service, err error) {
func selectServicesForUserTxn(txn *sql.Tx, userID id.UserID) (srvs []types.Service, err error) {
rows, err := txn.Query(selectServicesForUserSQL, userID) rows, err := txn.Query(selectServicesForUserSQL, userID)
if err != nil { if err != nil {
return return
@ -246,7 +247,7 @@ func selectServicesByTypeTxn(txn *sql.Tx, serviceType string) (srvs []types.Serv
for rows.Next() { for rows.Next() {
var s types.Service var s types.Service
var serviceID string var serviceID string
var serviceUserID string
var serviceUserID id.UserID
var serviceJSON []byte var serviceJSON []byte
if err = rows.Scan(&serviceID, &serviceUserID, &serviceJSON); err != nil { if err = rows.Scan(&serviceID, &serviceUserID, &serviceJSON); err != nil {
return return
@ -369,7 +370,7 @@ const deleteAuthSessionSQL = `
DELETE FROM auth_sessions WHERE realm_id=$1 AND user_id=$2 DELETE FROM auth_sessions WHERE realm_id=$1 AND user_id=$2
` `
func deleteAuthSessionTxn(txn *sql.Tx, realmID, userID string) error {
func deleteAuthSessionTxn(txn *sql.Tx, realmID string, userID id.UserID) error {
_, err := txn.Exec(deleteAuthSessionSQL, realmID, userID) _, err := txn.Exec(deleteAuthSessionSQL, realmID, userID)
return err return err
} }
@ -380,7 +381,7 @@ SELECT session_id, realm_type, realm_json, session_json FROM auth_sessions
WHERE auth_sessions.realm_id = $1 AND auth_sessions.user_id = $2 WHERE auth_sessions.realm_id = $1 AND auth_sessions.user_id = $2
` `
func selectAuthSessionByUserTxn(txn *sql.Tx, realmID, userID string) (types.AuthSession, error) {
func selectAuthSessionByUserTxn(txn *sql.Tx, realmID string, userID id.UserID) (types.AuthSession, error) {
var id string var id string
var realmType string var realmType string
var realmJSON []byte var realmJSON []byte
@ -409,12 +410,12 @@ SELECT user_id, realm_type, realm_json, session_json FROM auth_sessions
WHERE auth_sessions.realm_id = $1 AND auth_sessions.session_id = $2 WHERE auth_sessions.realm_id = $1 AND auth_sessions.session_id = $2
` `
func selectAuthSessionByIDTxn(txn *sql.Tx, realmID, id string) (types.AuthSession, error) {
var userID string
func selectAuthSessionByIDTxn(txn *sql.Tx, realmID, sid string) (types.AuthSession, error) {
var userID id.UserID
var realmType string var realmType string
var realmJSON []byte var realmJSON []byte
var sessionJSON []byte var sessionJSON []byte
row := txn.QueryRow(selectAuthSessionByIDSQL, realmID, id)
row := txn.QueryRow(selectAuthSessionByIDSQL, realmID, sid)
if err := row.Scan(&userID, &realmType, &realmJSON, &sessionJSON); err != nil { if err := row.Scan(&userID, &realmType, &realmJSON, &sessionJSON); err != nil {
return nil, err return nil, err
} }
@ -422,7 +423,7 @@ func selectAuthSessionByIDTxn(txn *sql.Tx, realmID, id string) (types.AuthSessio
if err != nil { if err != nil {
return nil, err return nil, err
} }
session := realm.AuthSession(id, userID, realmID)
session := realm.AuthSession(sid, userID, realmID)
if session == nil { if session == nil {
return nil, fmt.Errorf("Cannot create session for given realm") return nil, fmt.Errorf("Cannot create session for given realm")
} }
@ -454,7 +455,7 @@ const selectBotOptionsSQL = `
SELECT bot_options_json, set_by_user_id FROM bot_options WHERE user_id = $1 AND room_id = $2 SELECT bot_options_json, set_by_user_id FROM bot_options WHERE user_id = $1 AND room_id = $2
` `
func selectBotOptionsTxn(txn *sql.Tx, userID, roomID string) (opts types.BotOptions, err error) {
func selectBotOptionsTxn(txn *sql.Tx, userID id.UserID, roomID id.RoomID) (opts types.BotOptions, err error) {
var optionsJSON []byte var optionsJSON []byte
err = txn.QueryRow(selectBotOptionsSQL, userID, roomID).Scan(&optionsJSON, &opts.SetByUserID) err = txn.QueryRow(selectBotOptionsSQL, userID, roomID).Scan(&optionsJSON, &opts.SetByUserID)
if err != nil { if err != nil {

14
go.mod

@ -17,7 +17,7 @@ require (
github.com/gogo/protobuf v1.1.1 // indirect github.com/gogo/protobuf v1.1.1 // indirect
github.com/golang/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.4.0 // 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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195
github.com/json-iterator/go v1.1.9 // indirect github.com/json-iterator/go v1.1.9 // indirect
@ -25,7 +25,6 @@ require (
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.3.0 github.com/lib/pq v1.3.0
github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b
github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7
github.com/mattn/go-shellwords v1.0.10 github.com/mattn/go-shellwords v1.0.10
github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible
@ -36,21 +35,22 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect
github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77 github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77
github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335 // indirect github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335 // indirect
github.com/prometheus/common v0.0.0-20161002210234-85637ea67b04 // indirect github.com/prometheus/common v0.0.0-20161002210234-85637ea67b04 // indirect
github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 // indirect github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 // indirect
github.com/russross/blackfriday v1.5.2 github.com/russross/blackfriday v1.5.2
github.com/sasha-s/go-deadlock v0.2.0
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/dl v0.0.0-20200601221412-a954fa24b3e5 // indirect
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c // indirect golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
maunium.net/go/gomuks v0.1.0
maunium.net/go/mautrix v0.4.7
) )

74
go.sum

@ -1,6 +1,11 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.2/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -17,7 +22,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dghubble/oauth1 v0.6.0 h1:m1yC01Ohc/eF38jwZ8JUjL1a+XHHXtGQgK+MxQbmSx0= github.com/dghubble/oauth1 v0.6.0 h1:m1yC01Ohc/eF38jwZ8JUjL1a+XHHXtGQgK+MxQbmSx0=
github.com/dghubble/oauth1 v0.6.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk= github.com/dghubble/oauth1 v0.6.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk=
@ -25,8 +32,11 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947 h1:U/5Sq2nJQ0XDyks+8ATghtHSuquIGq7JYrqSrvtR2dg= github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947 h1:U/5Sq2nJQ0XDyks+8ATghtHSuquIGq7JYrqSrvtR2dg=
github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947/go.mod h1:KsMcjmY1UCGl7ozPbdVPDOvLaFeXnptSvtNRczhxNto= github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947/go.mod h1:KsMcjmY1UCGl7ozPbdVPDOvLaFeXnptSvtNRczhxNto=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU=
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -39,6 +49,7 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v2.0.1-0.20160719063544-b5e5babef39c+incompatible h1:9bbdREkf94ZqDMJ3Nsy5cJYNswJW2Xiirp+YuuuGAKM= github.com/google/go-github v2.0.1-0.20160719063544-b5e5babef39c+incompatible h1:9bbdREkf94ZqDMJ3Nsy5cJYNswJW2Xiirp+YuuuGAKM=
github.com/google/go-github v2.0.1-0.20160719063544-b5e5babef39c+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v2.0.1-0.20160719063544-b5e5babef39c+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
@ -49,6 +60,7 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 h1:j0UEFmS7wSjAwKEIkgKBn8PRDfjcuggzr93R9wk53nQ= github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 h1:j0UEFmS7wSjAwKEIkgKBn8PRDfjcuggzr93R9wk53nQ=
@ -59,19 +71,26 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.2+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lithammer/fuzzysearch v1.1.0/go.mod h1:Bqx4wo8lTOFcJr3ckpY6HA9lEIOO0H5HrkJ5CsN56HQ=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b h1:xpcmnpfUImRC4O2SAS/dmTcJENDXvGmLUzey76V1R3Q= github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b h1:xpcmnpfUImRC4O2SAS/dmTcJENDXvGmLUzey76V1R3Q=
github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg= github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg=
github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d h1:Vf/EQgAfg8/CBUQv9te7UJreZ9iKKouB2gb8UIRM4jQ=
github.com/matrix-org/gomatrix v0.0.0-20200128155335-9e7906b6766d/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo=
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
@ -79,6 +98,7 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E= github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E=
github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU=
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
@ -90,9 +110,14 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77 h1:YXoHPWLq9PIcMoZg7znMmEzqYHBszdXSemwGQRJoiSk= github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77 h1:YXoHPWLq9PIcMoZg7znMmEzqYHBszdXSemwGQRJoiSk=
github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.1-0.20160916180340-5636dc67ae77/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -127,8 +152,14 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -138,13 +169,32 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
github.com/trivago/tgo v1.0.1 h1:bxatjJIXNIpV18bucU4Uk/LaoxvxuOlp/oowRHyncLQ= github.com/trivago/tgo v1.0.1 h1:bxatjJIXNIpV18bucU4Uk/LaoxvxuOlp/oowRHyncLQ=
github.com/trivago/tgo v1.0.1/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= github.com/trivago/tgo v1.0.1/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/dl v0.0.0-20200601221412-a954fa24b3e5 h1:LrG45X3Uq6Sb+SuDP5WXq1jUhjYbqOyqw92r+E8Q7n0=
golang.org/dl v0.0.0-20200601221412-a954fa24b3e5/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -157,6 +207,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -165,13 +217,21 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c h1:9WR4YuzLDuQMqEmLQrG0DiMmE2/HvX1dlrujzjmNVFg= golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c h1:9WR4YuzLDuQMqEmLQrG0DiMmE2/HvX1dlrujzjmNVFg=
golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@ -182,10 +242,20 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2/go.mod h1:s1Sn2yZos05Qfs7NKt867Xe18emOmtsO3eAKbDaon0o=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
maunium.net/go/gomuks v0.1.0 h1:Ra0But5Cr3UfCSzd456x8MzV6jXLisZG0gW7RZY1xnk=
maunium.net/go/gomuks v0.1.0/go.mod h1:UWB7mC4OcKINQ2+ygalRSsSvnROoiwdSxdVLrWTeco4=
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.4.2/go.mod h1:8Y+NqmROJyWYvvP4yPfX9tLM59VCfgE/kcQ0SeX68ho=
maunium.net/go/mautrix v0.4.7 h1:jpclbeGcuiHPIWZFZhQJoxgZKP9f+9OLBPtcDNMFV/o=
maunium.net/go/mautrix v0.4.7/go.mod h1:8Y+NqmROJyWYvvP4yPfX9tLM59VCfgE/kcQ0SeX68ho=
maunium.net/go/mauview v0.1.1/go.mod h1:3QBUiuLct9moP1LgDhCGIg0Ovxn38Bd2sGndnUOuj4o=
maunium.net/go/tcell v0.2.0/go.mod h1:9Apcb3lNNS6C6lCqKT9UFp7BTRzHXfWE+/tgufsAMho=

3
goneb.go

@ -19,13 +19,16 @@ import (
"github.com/matrix-org/go-neb/polling" "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/realms/jira"
_ "github.com/matrix-org/go-neb/services/alertmanager" _ "github.com/matrix-org/go-neb/services/alertmanager"
_ "github.com/matrix-org/go-neb/services/echo" _ "github.com/matrix-org/go-neb/services/echo"
_ "github.com/matrix-org/go-neb/services/giphy" _ "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/google"
_ "github.com/matrix-org/go-neb/services/guggy" _ "github.com/matrix-org/go-neb/services/guggy"
_ "github.com/matrix-org/go-neb/services/imgur" _ "github.com/matrix-org/go-neb/services/imgur"
_ "github.com/matrix-org/go-neb/services/jira" _ "github.com/matrix-org/go-neb/services/jira"
_ "github.com/matrix-org/go-neb/services/rssbot" _ "github.com/matrix-org/go-neb/services/rssbot"
_ "github.com/matrix-org/go-neb/services/slackapi" _ "github.com/matrix-org/go-neb/services/slackapi"

11
matrix/matrix.go

@ -5,21 +5,22 @@ import (
"github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
) )
// NEBStore implements the gomatrix.Storer interface.
// NEBStore implements the mautrix.Storer interface.
// //
// It persists the next batch token in the database, and includes a ClientConfig for the client. // It persists the next batch token in the database, and includes a ClientConfig for the client.
type NEBStore struct { type NEBStore struct {
gomatrix.InMemoryStore
mautrix.InMemoryStore
Database database.Storer Database database.Storer
ClientConfig api.ClientConfig ClientConfig api.ClientConfig
} }
// SaveNextBatch saves to the database. // SaveNextBatch saves to the database.
func (s *NEBStore) SaveNextBatch(userID, nextBatch string) {
func (s *NEBStore) SaveNextBatch(userID id.UserID, nextBatch string) {
if err := s.Database.UpdateNextBatch(userID, nextBatch); err != nil { if err := s.Database.UpdateNextBatch(userID, nextBatch); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
@ -30,7 +31,7 @@ func (s *NEBStore) SaveNextBatch(userID, nextBatch string) {
} }
// LoadNextBatch loads from the database. // LoadNextBatch loads from the database.
func (s *NEBStore) LoadNextBatch(userID string) string {
func (s *NEBStore) LoadNextBatch(userID id.UserID) string {
token, err := s.Database.LoadNextBatch(userID) token, err := s.Database.LoadNextBatch(userID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{

14
realms/github/github.go

@ -2,6 +2,7 @@
package github package github
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -14,6 +15,7 @@ import (
"github.com/matrix-org/go-neb/services/github/client" "github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix/id"
) )
// RealmType of the Github Realm // RealmType of the Github Realm
@ -41,7 +43,7 @@ type Realm struct {
// Session represents an authenticated github session // Session represents an authenticated github session
type Session struct { type Session struct {
id string id string
userID string
userID id.UserID
realmID string realmID string
// AccessToken is the github access token for the user // AccessToken is the github access token for the user
@ -86,7 +88,7 @@ func (s *Session) Info() interface{} {
} }
for { for {
// query for a list of possible projects // 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 { if err != nil {
logger.WithError(err).Print("Failed to query github projects on github.com") logger.WithError(err).Print("Failed to query github projects on github.com")
return nil return nil
@ -110,7 +112,7 @@ func (s *Session) Info() interface{} {
} }
// UserID returns the user_id who authorised with Github // UserID returns the user_id who authorised with Github
func (s *Session) UserID() string {
func (s *Session) UserID() id.UserID {
return s.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...." // "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) state, err := randomString(10)
if err != nil { if err != nil {
log.WithError(err).Print("Failed to generate state param") log.WithError(err).Print("Failed to generate state param")
@ -259,7 +261,7 @@ func (r *Realm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
return return
} }
r.redirectOr( 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 // 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{ return &Session{
id: id, id: id,
userID: userID, userID: userID,

15
realms/jira/jira.go

@ -19,6 +19,7 @@ import (
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
"maunium.net/go/mautrix/id"
) )
// RealmType of the JIRA realm // RealmType of the JIRA realm
@ -83,7 +84,7 @@ type Realm struct {
// The endpoint is dictated by the realm ID. // The endpoint is dictated by the realm ID.
type Session struct { type Session struct {
id string // request token id string // request token
userID string
userID id.UserID
realmID string realmID string
// Configuration fields // Configuration fields
@ -121,7 +122,7 @@ func (s *Session) Info() interface{} {
} }
// UserID returns the ID of the user performing the authentication. // UserID returns the ID of the user performing the authentication.
func (s *Session) UserID() string {
func (s *Session) UserID() id.UserID {
return s.userID return s.userID
} }
@ -203,7 +204,7 @@ func (r *Realm) Register() error {
// { // {
// "URL": "https://jira.somewhere.com/plugins/servlet/oauth/authorize?oauth_token=7yeuierbgweguiegrTbOT" // "URL": "https://jira.somewhere.com/plugins/servlet/oauth/authorize?oauth_token=7yeuierbgweguiegrTbOT"
// } // }
func (r *Realm) RequestAuthSession(userID string, req json.RawMessage) interface{} {
func (r *Realm) RequestAuthSession(userID id.UserID, req json.RawMessage) interface{} {
logger := log.WithField("jira_url", r.JIRAEndpoint) logger := log.WithField("jira_url", r.JIRAEndpoint)
// check if they supplied a redirect URL // check if they supplied a redirect URL
@ -298,7 +299,7 @@ func (r *Realm) OnReceiveRedirect(w http.ResponseWriter, req *http.Request) {
} }
// AuthSession returns a JIRASession with the given parameters // AuthSession returns a JIRASession with the given parameters
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{ return &Session{
id: id, id: id,
userID: userID, userID: userID,
@ -310,7 +311,7 @@ func (r *Realm) AuthSession(id, userID, realmID string) types.AuthSession {
// An authenticated client for userID will be used if one exists, else an // An authenticated client for userID will be used if one exists, else an
// unauthenticated client will be used, which may not be able to see the complete list // unauthenticated client will be used, which may not be able to see the complete list
// of projects. // of projects.
func (r *Realm) ProjectKeyExists(userID, projectKey string) (bool, error) {
func (r *Realm) ProjectKeyExists(userID id.UserID, projectKey string) (bool, error) {
cli, err := r.JIRAClient(userID, true) cli, err := r.JIRAClient(userID, true)
if err != nil { if err != nil {
return false, err return false, err
@ -344,7 +345,7 @@ func (r *Realm) ProjectKeyExists(userID, projectKey string) (bool, error) {
// JIRAClient returns an authenticated jira.Client for the given userID. Returns an unauthenticated // JIRAClient returns an authenticated jira.Client for the given userID. Returns an unauthenticated
// client if allowUnauth is true and no authenticated session is found, else returns an error. // client if allowUnauth is true and no authenticated session is found, else returns an error.
func (r *Realm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, error) {
func (r *Realm) JIRAClient(userID id.UserID, allowUnauth bool) (*jira.Client, error) {
// Check if user has an auth session. // Check if user has an auth session.
session, err := database.GetServiceDB().LoadAuthSessionByUser(r.id, userID) session, err := database.GetServiceDB().LoadAuthSessionByUser(r.id, userID)
if err != nil { if err != nil {
@ -367,7 +368,7 @@ func (r *Realm) JIRAClient(userID string, allowUnauth bool) (*jira.Client, error
// make an unauthenticated client // make an unauthenticated client
return jira.NewClient(nil, r.JIRAEndpoint) return jira.NewClient(nil, r.JIRAEndpoint)
} }
return nil, errors.New("No authenticated session found for " + userID)
return nil, errors.New("No authenticated session found for " + userID.String())
} }
// make an authenticated client // make an authenticated client
auth := r.oauth1Config(r.JIRAEndpoint) auth := r.oauth1Config(r.JIRAEndpoint)

33
services/alertmanager/alertmanager.go

@ -5,14 +5,17 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus"
html "html/template" html "html/template"
"net/http" "net/http"
"strings" "strings"
text "text/template" text "text/template"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Alertmanager service. // ServiceType of the Alertmanager service.
@ -46,10 +49,10 @@ type Service struct {
// The URL which should be added to alertmanagers config - Populated by Go-NEB after Service registration. // The URL which should be added to alertmanagers config - Populated by Go-NEB after Service registration.
WebhookURL string `json:"webhook_url"` WebhookURL string `json:"webhook_url"`
// A map of matrix rooms to templates // A map of matrix rooms to templates
Rooms map[string]struct {
Rooms map[id.RoomID]struct {
TextTemplate string `json:"text_template"` TextTemplate string `json:"text_template"`
HTMLTemplate string `json:"html_template"` HTMLTemplate string `json:"html_template"`
MsgType string `json:"msg_type"`
MsgType mevt.MessageType `json:"msg_type"`
} `json:"rooms"` } `json:"rooms"`
} }
@ -75,7 +78,7 @@ type WebhookNotification struct {
} }
// OnReceiveWebhook receives requests from Alertmanager and sends requests to Matrix as a result. // OnReceiveWebhook receives requests from Alertmanager and sends requests to Matrix as a result.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
decoder := json.NewDecoder(req.Body) decoder := json.NewDecoder(req.Body)
var notif WebhookNotification var notif WebhookNotification
if err := decoder.Decode(&notif); err != nil { if err := decoder.Decode(&notif); err != nil {
@ -115,14 +118,14 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
w.WriteHeader(500) w.WriteHeader(500)
return return
} }
msg = gomatrix.HTMLMessage{
msg = mevt.MessageEventContent{
Body: bodyBuffer.String(), Body: bodyBuffer.String(),
MsgType: templates.MsgType, MsgType: templates.MsgType,
Format: "org.matrix.custom.html",
Format: mevt.FormatHTML,
FormattedBody: formattedBodyBuffer.String(), FormattedBody: formattedBodyBuffer.String(),
} }
} else { } else {
msg = gomatrix.TextMessage{
msg = mevt.MessageEventContent{
Body: bodyBuffer.String(), Body: bodyBuffer.String(),
MsgType: templates.MsgType, MsgType: templates.MsgType,
} }
@ -132,7 +135,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
"message": msg, "message": msg,
"room_id": roomID, "room_id": roomID,
}).Print("Sending Alertmanager notification to room") }).Print("Sending Alertmanager notification to room")
if _, e := cli.SendMessageEvent(roomID, "m.room.message", msg); e != nil {
if _, e := cli.SendMessageEvent(roomID, mevt.EventMessage, msg); e != nil {
log.WithError(e).WithField("room_id", roomID).Print( log.WithError(e).WithField("room_id", roomID).Print(
"Failed to send Alertmanager notification to room.") "Failed to send Alertmanager notification to room.")
} }
@ -141,7 +144,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
} }
// Register makes sure the Config information supplied is valid. // Register makes sure the Config information supplied is valid.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
s.WebhookURL = s.webhookEndpointURL s.WebhookURL = s.webhookEndpointURL
for _, templates := range s.Rooms { for _, templates := range s.Rooms {
// validate that we have at least a plain text template // validate that we have at least a plain text template
@ -188,9 +191,9 @@ func (s *Service) PostRegister(oldService types.Service) {
} }
} }
func (s *Service) joinRooms(client *gomatrix.Client) {
func (s *Service) joinRooms(client *mautrix.Client) {
for roomID := range s.Rooms { for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID, "", nil); err != nil {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
"room_id": roomID, "room_id": roomID,
@ -201,7 +204,7 @@ func (s *Service) joinRooms(client *gomatrix.Client) {
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
webhookEndpointURL: webhookEndpointURL, webhookEndpointURL: webhookEndpointURL,

18
services/alertmanager/alertmanager_test.go

@ -4,10 +4,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"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"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -15,13 +11,19 @@ import (
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
) )
func TestNotify(t *testing.T) { func TestNotify(t *testing.T) {
database.SetServiceDB(&database.NopStorage{}) database.SetServiceDB(&database.NopStorage{})
// Intercept message sending to Matrix and mock responses // Intercept message sending to Matrix and mock responses
msgs := []gomatrix.HTMLMessage{}
msgs := []mevt.MessageEventContent{}
matrixCli := buildTestClient(&msgs) matrixCli := buildTestClient(&msgs)
// create the service // create the service
@ -91,13 +93,13 @@ func TestNotify(t *testing.T) {
} }
} }
func buildTestClient(msgs *[]gomatrix.HTMLMessage) *gomatrix.Client {
func buildTestClient(msgs *[]mevt.MessageEventContent) *mautrix.Client {
matrixTrans := struct{ testutils.MockTransport }{} matrixTrans := struct{ testutils.MockTransport }{}
matrixTrans.RT = func(req *http.Request) (*http.Response, error) { matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if !strings.Contains(req.URL.String(), "/send/m.room.message") { if !strings.Contains(req.URL.String(), "/send/m.room.message") {
return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String()) return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String())
} }
var msg gomatrix.HTMLMessage
var msg mevt.MessageEventContent
if err := json.NewDecoder(req.Body).Decode(&msg); err != nil { if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
return nil, fmt.Errorf("Failed to decode request JSON: %s", err) return nil, fmt.Errorf("Failed to decode request JSON: %s", err)
} }
@ -107,7 +109,7 @@ func buildTestClient(msgs *[]gomatrix.HTMLMessage) *gomatrix.Client {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)),
}, nil }, nil
} }
matrixCli, _ := gomatrix.NewClient("https://hs", "@neb:hs", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hs", "@neb:hs", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
return matrixCli return matrixCli
} }

15
services/echo/echo.go

@ -5,7 +5,9 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Echo service // ServiceType of the Echo service
@ -19,19 +21,22 @@ type Service struct {
// Commands supported: // Commands supported:
// !echo some message // !echo some message
// Responds with a notice of "some message". // Responds with a notice of "some message".
func (e *Service) Commands(cli *gomatrix.Client) []types.Command {
func (e *Service) Commands(cli *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{ types.Command{
Path: []string{"echo"}, Path: []string{"echo"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return &gomatrix.TextMessage{"m.notice", strings.Join(args, " ")}, nil
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: strings.Join(args, " "),
}, nil
}, },
}, },
} }
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

29
services/giphy/giphy.go

@ -10,8 +10,11 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Giphy service. // ServiceType of the Giphy service.
@ -58,18 +61,18 @@ type Service struct {
// Commands supported: // Commands supported:
// !giphy some search query without quotes // !giphy some search query without quotes
// Responds with a suitable GIF into the same room as the command. // Responds with a suitable GIF into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
func (s *Service) Commands(client *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{ types.Command{
Path: []string{"giphy"}, Path: []string{"giphy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdGiphy(client, roomID, userID, args) return s.cmdGiphy(client, roomID, userID, args)
}, },
}, },
} }
} }
func (s *Service) cmdGiphy(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGiphy(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// only 1 arg which is the text to search for. // only 1 arg which is the text to search for.
query := strings.Join(args, " ") query := strings.Join(args, " ")
gifResult, err := s.searchGiphy(query) gifResult, err := s.searchGiphy(query)
@ -90,14 +93,14 @@ func (s *Service) cmdGiphy(client *gomatrix.Client, roomID, userID string, args
return nil, err return nil, err
} }
return gomatrix.ImageMessage{
MsgType: "m.image",
return mevt.MessageEventContent{
MsgType: event.MsgImage,
Body: gifResult.Slug, Body: gifResult.Slug,
URL: resUpload.ContentURI,
Info: gomatrix.ImageInfo{
URL: resUpload.ContentURI.CUString(),
Info: &mevt.FileInfo{
Height: asInt(image.Height), Height: asInt(image.Height),
Width: asInt(image.Width), Width: asInt(image.Width),
Mimetype: "image/gif",
MimeType: "image/gif",
Size: asInt(image.Size), Size: asInt(image.Size),
}, },
}, nil }, nil
@ -130,16 +133,16 @@ func (s *Service) searchGiphy(query string) (*result, error) {
return &search.Data, nil return &search.Data, nil
} }
func asInt(strInt string) uint {
u64, err := strconv.ParseUint(strInt, 10, 32)
func asInt(strInt string) int {
i64, err := strconv.ParseInt(strInt, 10, 32)
if err != nil { if err != nil {
return 0 // default to 0 since these are all just hints to the client return 0 // default to 0 since these are all just hints to the client
} }
return uint(u64)
return int(i64)
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

215
services/github/github.go

@ -5,6 +5,7 @@
package github package github
import ( import (
"context"
"database/sql" "database/sql"
"fmt" "fmt"
"regexp" "regexp"
@ -12,15 +13,18 @@ import (
"strings" "strings"
"bytes" "bytes"
"html"
gogithub "github.com/google/go-github/github" gogithub "github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/realms/github" "github.com/matrix-org/go-neb/realms/github"
"github.com/matrix-org/go-neb/services/github/client" "github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" 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 // ServiceType of the Github service
@ -69,7 +73,7 @@ type Service struct {
RealmID string 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) cli = s.githubClientFor(userID, false)
if cli == nil { if cli == nil {
var r types.AuthRealm var r types.AuthRealm
@ -91,14 +95,17 @@ func (s *Service) requireGithubClientFor(userID string) (cli *gogithub.Client, r
const numberGithubSearchSummaries = 3 const numberGithubSearchSummaries = 3
const cmdGithubSearchUsage = `!github search "search query"` 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) cli := s.githubClientFor(userID, true)
if len(args) < 2 { 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, " ") 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 { if err != nil {
log.WithField("err", err).Print("Failed to search") 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 { 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 numResults := *searchResult.Total
@ -130,9 +140,9 @@ func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interfa
} }
htmlBuffer.WriteString("</ol>") htmlBuffer.WriteString("</ol>")
return &gomatrix.HTMLMessage{
return &mevt.MessageEventContent{
Body: plainBuffer.String(), Body: plainBuffer.String(),
MsgType: "m.notice",
MsgType: mevt.MsgNotice,
Format: "org.matrix.custom.html", Format: "org.matrix.custom.html",
FormattedBody: htmlBuffer.String(), FormattedBody: htmlBuffer.String(),
}, nil }, nil
@ -140,13 +150,16 @@ func (s *Service) cmdGithubSearch(roomID, userID string, args []string) (interfa
const cmdGithubCreateUsage = `!github create [owner/repo] "issue title" "description"` 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) cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil { if cli == nil {
return resp, err return resp, err
} }
if len(args) == 0 { 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: // 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 // look for a default repo
defaultRepo := s.defaultRepo(roomID) defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" { 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 // default repo should pass the regexp
ownerRepoGroups = ownerRepoRegex.FindStringSubmatch(defaultRepo) ownerRepoGroups = ownerRepoRegex.FindStringSubmatch(defaultRepo)
if len(ownerRepoGroups) == 0 { 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 // 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 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, Title: title,
Body: desc, 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 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{ 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:)` 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) cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil { if cli == nil {
return resp, err return resp, err
} }
if len(args) < 2 { 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]] reaction, ok := cmdGithubReactAliases[args[1]]
if !ok { 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] // 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 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 { if err != nil {
log.WithField("err", err).Print("Failed to react to issue") 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 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"` 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) cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil { if cli == nil {
return resp, err return resp, err
} }
if len(args) == 0 { 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] // 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 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, 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 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] [...]` 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) cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil { if cli == nil {
return resp, err return resp, err
} }
if len(args) < 1 { 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 { } 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] // 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 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 { if err != nil {
log.WithField("err", err).Print("Failed to add issue assignees") 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 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) cli, resp, err := s.requireGithubClientFor(userID)
if cli == nil { if cli == nil {
return resp, err return resp, err
} }
if len(args) == 0 { 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] // 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 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, 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 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` 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) return s.githubIssueCloseReopen(roomID, userID, args, "closed", "close", cmdGithubCloseUsage)
} }
const cmdGithubReopenUsage = `!github reopen [owner/repo]#issue` 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) 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: // We expect the input to look like:
// "[owner/repo]#issue" // "[owner/repo]#issue"
// They can omit the owner/repo if there is a default one set. // 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) ownerRepoIssueGroups := ownerRepoIssueRegexAnchored.FindStringSubmatch(input)
if len(ownerRepoIssueGroups) != 5 { if len(ownerRepoIssueGroups) != 5 {
resp = &gomatrix.TextMessage{"m.notice", "Usage: " + usage}
resp = &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: " + usage,
}
return return
} }
@ -400,7 +450,10 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
var err error var err error
if issueNum, err = strconv.Atoi(ownerRepoIssueGroups[4]); err != nil { 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 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 // issue only match, this only works if there is a default repo
defaultRepo := s.defaultRepo(roomID) defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" { 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 return
} }
segs := strings.Split(defaultRepo, "/") segs := strings.Split(defaultRepo, "/")
if len(segs) != 2 { 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 return
} }
@ -424,10 +483,10 @@ func (s *Service) getIssueDetailsFor(input, roomID, usage string) (owner, repo s
return 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) 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 { if err != nil {
log.WithError(err).WithFields(log.Fields{ log.WithError(err).WithFields(log.Fields{
"owner": owner, "owner": owner,
@ -437,16 +496,16 @@ func (s *Service) expandIssue(roomID, userID, owner, repo string, issueNum int)
return nil 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) 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 { if err != nil {
log.WithError(err).WithFields(log.Fields{ log.WithError(err).WithFields(log.Fields{
"owner": owner, "owner": owner,
@ -487,10 +546,10 @@ func (s *Service) expandCommit(roomID, userID, owner, repo, sha string) interfac
plainBuffer.WriteString(segs[0]) plainBuffer.WriteString(segs[0])
} }
return &gomatrix.HTMLMessage{
return &mevt.MessageEventContent{
Body: plainBuffer.String(), Body: plainBuffer.String(),
MsgType: "m.notice",
Format: "org.matrix.custom.html",
MsgType: mevt.MsgNotice,
Format: mevt.FormatHTML,
FormattedBody: htmlBuffer.String(), 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 // 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 // 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. // 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{ return []types.Command{
types.Command{
{
Path: []string{"github", "search"}, 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) return s.cmdGithubSearch(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "create"}, 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) return s.cmdGithubCreate(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "react"}, 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) return s.cmdGithubReact(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "comment"}, 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) return s.cmdGithubComment(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "assign"}, 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) return s.cmdGithubAssign(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "close"}, 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) return s.cmdGithubClose(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "reopen"}, 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) return s.cmdGithubReopen(roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"github", "help"}, 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, cmdGithubCreateUsage,
cmdGithubReactUsage, cmdGithubReactUsage,
cmdGithubCommentUsage, cmdGithubCommentUsage,
@ -573,11 +632,11 @@ func (s *Service) Commands(cli *gomatrix.Client) []types.Command {
// it will also expand strings of the form: // it will also expand strings of the form:
// #12 // #12
// using the default repository. // 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{ return []types.Expansion{
types.Expansion{ types.Expansion{
Regexp: ownerRepoIssueRegex, 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: // There's an optional group in the regex so matchingGroups can look like:
// [foo/bar#55 foo bar 55] // [foo/bar#55 foo bar 55]
// [#55 55] // [#55 55]
@ -619,7 +678,7 @@ func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
}, },
types.Expansion{ types.Expansion{
Regexp: ownerRepoCommitRegex, 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: // There's an optional group in the regex so matchingGroups can look like:
// [foo/bar@a123 foo bar a123] // [foo/bar@a123 foo bar a123]
// [@a123 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. // 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 == "" { if s.RealmID == "" {
return fmt.Errorf("RealmID is required") 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. // 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{ logger := log.WithFields(log.Fields{
"room_id": roomID, "room_id": roomID,
"bot_user_id": s.ServiceUserID(), "bot_user_id": s.ServiceUserID(),
@ -707,7 +766,7 @@ func (s *Service) defaultRepo(roomID string) string {
return defaultRepo 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) token, err := getTokenForUser(s.RealmID, userID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ 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) realm, err := database.GetServiceDB().LoadAuthRealm(realmID)
if err != nil { if err != nil {
return "", err return "", err
@ -750,7 +809,7 @@ func getTokenForUser(realmID, userID string) (string, error) {
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

29
services/github/github_webhook.go

@ -1,6 +1,7 @@
package github package github
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"sort" "sort"
@ -11,8 +12,10 @@ import (
"github.com/matrix-org/go-neb/services/github/client" "github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/services/github/webhook" "github.com/matrix-org/go-neb/services/github/webhook"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" 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. // WebhookServiceType of the Github Webhook service.
@ -45,12 +48,12 @@ type WebhookService struct {
types.DefaultService types.DefaultService
webhookEndpointURL string webhookEndpointURL string
// The user ID to create/delete webhooks as. // 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 ID of an existing "github" realm. This realm will be used to obtain
// the Github credentials of the ClientUserID. // the Github credentials of the ClientUserID.
RealmID string RealmID string
// A map from Matrix room ID to Github "owner/repo"-style repositories. // 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. // A map of "owner/repo"-style repositories to the events to listen for.
Repos map[string]struct { // owner/repo => { events: ["push","issue","pull_request"] } Repos map[string]struct { // owner/repo => { events: ["push","issue","pull_request"] }
// The webhook events to listen for. Currently supported: // 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 // If the "owner/repo" string doesn't exist in this Service config, then the webhook will be deleted from
// Github. // 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) evType, repo, msg, err := webhook.OnReceiveRequest(req, s.SecretToken)
if err != nil { if err != nil {
w.WriteHeader(err.Code) w.WriteHeader(err.Code)
@ -108,7 +111,7 @@ func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Reque
"message": msg, "message": msg,
"room_id": roomID, "room_id": roomID,
}).Print("Sending notification to room") }).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( logger.WithError(e).WithField("room_id", roomID).Print(
"Failed to send notification to room.") "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 // 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. // 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 == "" { if s.RealmID == "" || s.ClientUserID == "" {
return fmt.Errorf("RealmID and ClientUserID is required") 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 { 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? // TODO: Leave the rooms we successfully joined?
return err return err
} }
@ -300,7 +303,7 @@ func (s *WebhookService) createHook(cli *gogithub.Client, ownerRepo string) erro
cfg["secret"] = s.SecretToken cfg["secret"] = s.SecretToken
} }
events := []string{"push", "pull_request", "issues", "issue_comment", "pull_request_review_comment"} 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, Name: &name,
Config: cfg, Config: cfg,
Events: events, 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 // 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. // 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 { if err != nil {
return err 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) 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 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) token, err := getTokenForUser(s.RealmID, userID)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -462,7 +465,7 @@ func (s *WebhookService) loadRealm() (types.AuthRealm, error) {
} }
func init() { 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{ return &WebhookService{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, WebhookServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, WebhookServiceType),
webhookEndpointURL: webhookEndpointURL, 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/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "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" var roomID = "!testroom:id"
@ -22,13 +23,13 @@ func TestGithubWebhook(t *testing.T) {
database.SetServiceDB(&database.NopStorage{}) database.SetServiceDB(&database.NopStorage{})
// Intercept message sending to Matrix and mock responses // Intercept message sending to Matrix and mock responses
msgs := []gomatrix.TextMessage{}
msgs := []mevt.MessageEventContent{}
matrixTrans := struct{ testutils.MockTransport }{} matrixTrans := struct{ testutils.MockTransport }{}
matrixTrans.RT = func(req *http.Request) (*http.Response, error) { matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if !strings.Contains(req.URL.String(), "/send/m.room.message") { if !strings.Contains(req.URL.String(), "/send/m.room.message") {
return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String()) 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 { if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
return nil, fmt.Errorf("Failed to decode request JSON: %s", err) 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"}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)),
}, nil }, 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} matrixCli.Client = &http.Client{Transport: matrixTrans}
// create the service // create the service

7
services/github/webhook/webhook.go

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

48
services/google/google.go

@ -12,8 +12,10 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Google service // ServiceType of the Google service
@ -68,23 +70,23 @@ type Service struct {
// Commands supported: // Commands supported:
// !google image some_search_query_without_quotes // !google image some_search_query_without_quotes
// Responds with a suitable image into the same room as the command. // Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
func (s *Service) Commands(client *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{
{
Path: []string{"google", "image"}, Path: []string{"google", "image"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdGoogleImgSearch(client, roomID, userID, args) return s.cmdGoogleImgSearch(client, roomID, userID, args)
}, },
}, },
types.Command{
{
Path: []string{"google", "help"}, Path: []string{"google", "help"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return usageMessage(), nil return usageMessage(), nil
}, },
}, },
types.Command{
{
Path: []string{"google"}, Path: []string{"google"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return usageMessage(), nil return usageMessage(), nil
}, },
}, },
@ -92,12 +94,14 @@ func (s *Service) Commands(client *gomatrix.Client) []types.Command {
} }
// usageMessage returns a matrix TextMessage representation of the service usage // usageMessage returns a matrix TextMessage representation of the service usage
func usageMessage() *gomatrix.TextMessage {
return &gomatrix.TextMessage{"m.notice",
`Usage: !google image image_search_text`}
func usageMessage() *mevt.MessageEventContent {
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: `Usage: !google image image_search_text`,
}
} }
func (s *Service) cmdGoogleImgSearch(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGoogleImgSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
if len(args) < 1 { if len(args) < 1 {
return usageMessage(), nil return usageMessage(), nil
@ -114,8 +118,8 @@ func (s *Service) cmdGoogleImgSearch(client *gomatrix.Client, roomID, userID str
var imgURL = searchResult.Link var imgURL = searchResult.Link
if imgURL == "" { if imgURL == "" {
return gomatrix.TextMessage{
MsgType: "m.notice",
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "No image found!", Body: "No image found!",
}, nil }, nil
} }
@ -126,14 +130,14 @@ func (s *Service) cmdGoogleImgSearch(client *gomatrix.Client, roomID, userID str
return nil, fmt.Errorf("Failed to upload Google image at URL %s (content type %s) to matrix: %s", imgURL, searchResult.Mime, err.Error()) return nil, fmt.Errorf("Failed to upload Google image at URL %s (content type %s) to matrix: %s", imgURL, searchResult.Mime, err.Error())
} }
return gomatrix.ImageMessage{
MsgType: "m.image",
return mevt.MessageEventContent{
MsgType: mevt.MsgImage,
Body: querySentence, Body: querySentence,
URL: resUpload.ContentURI,
Info: gomatrix.ImageInfo{
Height: uint(math.Floor(searchResult.Image.Height)),
Width: uint(math.Floor(searchResult.Image.Width)),
Mimetype: searchResult.Mime,
URL: resUpload.ContentURI.CUString(),
Info: &mevt.FileInfo{
Height: int(math.Floor(searchResult.Image.Height)),
Width: int(math.Floor(searchResult.Image.Width)),
MimeType: searchResult.Mime,
}, },
}, nil }, nil
} }
@ -195,7 +199,7 @@ func response2String(res *http.Response) string {
// Initialise the service // Initialise the service
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

4
services/google/google_test.go

@ -12,7 +12,7 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
) )
// TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make // TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make
@ -101,7 +101,7 @@ func TestCommand(t *testing.T) {
} }
return nil, fmt.Errorf("Unknown URL: %s", req.URL.String()) return nil, fmt.Errorf("Unknown URL: %s", req.URL.String())
} }
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@googlebot:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@googlebot:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
// Execute the matrix !command // Execute the matrix !command

30
services/guggy/guggy.go

@ -11,8 +11,10 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Guggy service // ServiceType of the Guggy service
@ -49,17 +51,17 @@ type Service struct {
// Commands supported: // Commands supported:
// !guggy some search query without quotes // !guggy some search query without quotes
// Responds with a suitable GIF into the same room as the command. // Responds with a suitable GIF into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
func (s *Service) Commands(client *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{
{
Path: []string{"guggy"}, Path: []string{"guggy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdGuggy(client, roomID, userID, args) return s.cmdGuggy(client, roomID, userID, args)
}, },
}, },
} }
} }
func (s *Service) cmdGuggy(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdGuggy(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// only 1 arg which is the text to search for. // only 1 arg which is the text to search for.
querySentence := strings.Join(args, " ") querySentence := strings.Join(args, " ")
gifResult, err := s.text2gifGuggy(querySentence) gifResult, err := s.text2gifGuggy(querySentence)
@ -68,8 +70,8 @@ func (s *Service) cmdGuggy(client *gomatrix.Client, roomID, userID string, args
} }
if gifResult.GIF == "" { if gifResult.GIF == "" {
return gomatrix.TextMessage{
MsgType: "m.notice",
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "No GIF found!", Body: "No GIF found!",
}, nil }, nil
} }
@ -79,14 +81,14 @@ func (s *Service) cmdGuggy(client *gomatrix.Client, roomID, userID string, args
return nil, fmt.Errorf("Failed to upload Guggy image to matrix: %s", err.Error()) return nil, fmt.Errorf("Failed to upload Guggy image to matrix: %s", err.Error())
} }
return gomatrix.ImageMessage{
return mevt.MessageEventContent{
MsgType: "m.image", MsgType: "m.image",
Body: querySentence, Body: querySentence,
URL: resUpload.ContentURI,
Info: gomatrix.ImageInfo{
Height: uint(math.Floor(gifResult.Height)),
Width: uint(math.Floor(gifResult.Width)),
Mimetype: "image/gif",
URL: resUpload.ContentURI.CUString(),
Info: &mevt.FileInfo{
Height: int(math.Floor(gifResult.Height)),
Width: int(math.Floor(gifResult.Width)),
MimeType: "image/gif",
}, },
}, nil }, nil
} }
@ -142,7 +144,7 @@ func (s *Service) text2gifGuggy(querySentence string) (*guggyGifResult, error) {
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

4
services/guggy/guggy_test.go

@ -12,7 +12,7 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
) )
// TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make // TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make
@ -86,7 +86,7 @@ func TestCommand(t *testing.T) {
} }
return nil, fmt.Errorf("Unknown URL: %s", req.URL.String()) return nil, fmt.Errorf("Unknown URL: %s", req.URL.String())
} }
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@guggybot:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@guggybot:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
// Execute the matrix !command // Execute the matrix !command

50
services/imgur/imgur.go

@ -11,8 +11,10 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Imgur service // ServiceType of the Imgur service
@ -114,17 +116,17 @@ type Service struct {
// Commands supported: // Commands supported:
// !imgur some_search_query_without_quotes // !imgur some_search_query_without_quotes
// Responds with a suitable image into the same room as the command. // Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
func (s *Service) Commands(client *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{
{
Path: []string{"imgur", "help"}, Path: []string{"imgur", "help"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return usageMessage(), nil return usageMessage(), nil
}, },
}, },
types.Command{
{
Path: []string{"imgur"}, Path: []string{"imgur"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdImgSearch(client, roomID, userID, args) return s.cmdImgSearch(client, roomID, userID, args)
}, },
}, },
@ -132,13 +134,15 @@ func (s *Service) Commands(client *gomatrix.Client) []types.Command {
} }
// usageMessage returns a matrix TextMessage representation of the service usage // usageMessage returns a matrix TextMessage representation of the service usage
func usageMessage() *gomatrix.TextMessage {
return &gomatrix.TextMessage{"m.notice",
`Usage: !imgur image_search_text`}
func usageMessage() *mevt.MessageEventContent {
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: !imgur image_search_text",
}
} }
// Search Imgur for a relevant image and upload it to matrix // Search Imgur for a relevant image and upload it to matrix
func (s *Service) cmdImgSearch(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdImgSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// Check for query text // Check for query text
if len(args) < 1 { if len(args) < 1 {
return usageMessage(), nil return usageMessage(), nil
@ -155,8 +159,8 @@ func (s *Service) cmdImgSearch(client *gomatrix.Client, roomID, userID string, a
if searchResultImage != nil { if searchResultImage != nil {
var imgURL = searchResultImage.Link var imgURL = searchResultImage.Link
if imgURL == "" { if imgURL == "" {
return gomatrix.TextMessage{
MsgType: "m.notice",
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "No image found!", Body: "No image found!",
}, nil }, nil
} }
@ -168,24 +172,24 @@ func (s *Service) cmdImgSearch(client *gomatrix.Client, roomID, userID string, a
} }
// Return image message // Return image message
return gomatrix.ImageMessage{
return mevt.MessageEventContent{
MsgType: "m.image", MsgType: "m.image",
Body: querySentence, Body: querySentence,
URL: resUpload.ContentURI,
Info: gomatrix.ImageInfo{
Height: uint(searchResultImage.Height),
Width: uint(searchResultImage.Width),
Mimetype: searchResultImage.Type,
URL: resUpload.ContentURI.CUString(),
Info: &mevt.FileInfo{
Height: searchResultImage.Height,
Width: searchResultImage.Width,
MimeType: searchResultImage.Type,
}, },
}, nil }, nil
} else if searchResultAlbum != nil { } else if searchResultAlbum != nil {
return gomatrix.TextMessage{
MsgType: "m.notice",
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Search returned an album - Not currently supported", Body: "Search returned an album - Not currently supported",
}, nil }, nil
} else { } else {
return gomatrix.TextMessage{
MsgType: "m.notice",
return mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "No image found!", Body: "No image found!",
}, nil }, nil
} }
@ -280,7 +284,7 @@ func response2String(res *http.Response) string {
// Initialise the service // Initialise the service
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

4
services/imgur/imgur_test.go

@ -12,7 +12,7 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
) )
func TestCommand(t *testing.T) { func TestCommand(t *testing.T) {
@ -106,7 +106,7 @@ func TestCommand(t *testing.T) {
} }
return nil, fmt.Errorf("Unknown URL: %s", req.URL.String()) return nil, fmt.Errorf("Unknown URL: %s", req.URL.String())
} }
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@imgurbot:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@imgurbot:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
// Execute the matrix !command // Execute the matrix !command

43
services/jira/jira.go

@ -18,9 +18,12 @@ import (
"github.com/matrix-org/go-neb/realms/jira" "github.com/matrix-org/go-neb/realms/jira"
"github.com/matrix-org/go-neb/realms/jira/urls" "github.com/matrix-org/go-neb/realms/jira/urls"
"github.com/matrix-org/go-neb/services/jira/webhook" "github.com/matrix-org/go-neb/services/jira/webhook"
"github.com/matrix-org/go-neb/services/utils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the JIRA Service // ServiceType of the JIRA Service
@ -54,9 +57,9 @@ type Service struct {
webhookEndpointURL string webhookEndpointURL string
// The user ID to create issues as, or to create/delete webhooks as. This user // The user ID to create issues as, or to create/delete webhooks as. This user
// is also used to look up issues for expansions. // is also used to look up issues for expansions.
ClientUserID string
ClientUserID id.UserID
// A map from Matrix room ID to JIRA realms and project keys. // A map from Matrix room ID to JIRA realms and project keys.
Rooms map[string]struct {
Rooms map[id.RoomID]struct {
// A map of realm IDs to project keys. The realm IDs determine the JIRA // A map of realm IDs to project keys. The realm IDs determine the JIRA
// endpoint used. // endpoint used.
Realms map[string]struct { Realms map[string]struct {
@ -73,7 +76,7 @@ type Service struct {
// Register ensures that the given realm IDs are valid JIRA realms and registers webhooks // Register ensures that the given realm IDs are valid JIRA realms and registers webhooks
// with those JIRA endpoints. // with those JIRA endpoints.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
// We only ever make 1 JIRA webhook which listens for all projects and then filter // We only ever make 1 JIRA webhook which listens for all projects and then filter
// on receive. So we simply need to know if we need to make a webhook or not. We // on receive. So we simply need to know if we need to make a webhook or not. We
// need to do this for each unique realm. // need to do this for each unique realm.
@ -94,7 +97,7 @@ func (s *Service) Register(oldService types.Service, client *gomatrix.Client) er
return nil return nil
} }
func (s *Service) cmdJiraCreate(roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdJiraCreate(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// E.g jira create PROJ "Issue title" "Issue desc" // E.g jira create PROJ "Issue title" "Issue desc"
if len(args) <= 1 { if len(args) <= 1 {
return nil, errors.New("Missing project key (e.g 'ABC') and/or title") return nil, errors.New("Missing project key (e.g 'ABC') and/or title")
@ -164,13 +167,13 @@ func (s *Service) cmdJiraCreate(roomID, userID string, args []string) (interface
return nil, fmt.Errorf("Failed to create issue: JIRA returned %d", res.StatusCode) return nil, fmt.Errorf("Failed to create issue: JIRA returned %d", res.StatusCode)
} }
return &gomatrix.TextMessage{
"m.notice",
fmt.Sprintf("Created issue: %sbrowse/%s", r.JIRAEndpoint, i.Key),
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: fmt.Sprintf("Created issue: %sbrowse/%s", r.JIRAEndpoint, i.Key),
}, nil }, nil
} }
func (s *Service) expandIssue(roomID, userID string, issueKeyGroups []string) interface{} {
func (s *Service) expandIssue(roomID id.RoomID, userID id.UserID, issueKeyGroups []string) interface{} {
// issueKeyGroups => ["SYN-123", "SYN", "123"] // issueKeyGroups => ["SYN-123", "SYN", "123"]
if len(issueKeyGroups) != 3 { if len(issueKeyGroups) != 3 {
log.WithField("groups", issueKeyGroups).Error("Bad number of groups") log.WithField("groups", issueKeyGroups).Error("Bad number of groups")
@ -221,8 +224,8 @@ func (s *Service) expandIssue(roomID, userID string, issueKeyGroups []string) in
logger.WithError(err).Print("Failed to GET issue") logger.WithError(err).Print("Failed to GET issue")
return err return err
} }
return gomatrix.GetHTMLMessage(
"m.notice",
return utils.StrippedHTMLMessage(
mevt.MsgNotice,
fmt.Sprintf( fmt.Sprintf(
"%sbrowse/%s : %s", "%sbrowse/%s : %s",
jrealm.JIRAEndpoint, issueKey, htmlSummaryForIssue(issue), jrealm.JIRAEndpoint, issueKey, htmlSummaryForIssue(issue),
@ -239,11 +242,11 @@ func (s *Service) expandIssue(roomID, userID string, issueKeyGroups []string) in
// same project key, which project is chosen is undefined. If there // same project key, which project is chosen is undefined. If there
// is no JIRA account linked to the Matrix user ID, it will return a Starter Link // is no JIRA account linked to the Matrix user ID, it will return a Starter Link
// if there is a known public project with that project key. // if there is a known public project with that project key.
func (s *Service) Commands(cli *gomatrix.Client) []types.Command {
func (s *Service) Commands(cli *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{ types.Command{
Path: []string{"jira", "create"}, Path: []string{"jira", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdJiraCreate(roomID, userID, args) return s.cmdJiraCreate(roomID, userID, args)
}, },
}, },
@ -256,11 +259,11 @@ func (s *Service) Commands(cli *gomatrix.Client) []types.Command {
// to map the project key to a realm, and subsequently the JIRA endpoint to hit. // to map the project key to a realm, and subsequently the JIRA endpoint to hit.
// If there are multiple projects with the same project key in the Service Config, one will // If there are multiple projects with the same project key in the Service Config, one will
// be chosen arbitrarily. // be chosen arbitrarily.
func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
return []types.Expansion{ return []types.Expansion{
types.Expansion{ types.Expansion{
Regexp: issueKeyRegex, Regexp: issueKeyRegex,
Expand: func(roomID, userID string, issueKeyGroups []string) interface{} {
Expand: func(roomID id.RoomID, userID id.UserID, issueKeyGroups []string) interface{} {
return s.expandIssue(roomID, userID, issueKeyGroups) return s.expandIssue(roomID, userID, issueKeyGroups)
}, },
}, },
@ -268,7 +271,7 @@ func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion {
} }
// OnReceiveWebhook receives requests from JIRA and possibly sends requests to Matrix as a result. // OnReceiveWebhook receives requests from JIRA and possibly sends requests to Matrix as a result.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
eventProjectKey, event, httpErr := webhook.OnReceiveRequest(req) eventProjectKey, event, httpErr := webhook.OnReceiveRequest(req)
if httpErr != nil { if httpErr != nil {
log.Print("Failed to handle JIRA webhook") log.Print("Failed to handle JIRA webhook")
@ -297,7 +300,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
continue continue
} }
_, msgErr := cli.SendMessageEvent( _, msgErr := cli.SendMessageEvent(
roomID, "m.room.message", gomatrix.GetHTMLMessage("m.notice", htmlText),
roomID, mevt.EventMessage, utils.StrippedHTMLMessage(mevt.MsgNotice, htmlText),
) )
if msgErr != nil { if msgErr != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -312,7 +315,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
w.WriteHeader(200) w.WriteHeader(200)
} }
func (s *Service) realmIDForProject(roomID, projectKey string) string {
func (s *Service) realmIDForProject(roomID id.RoomID, projectKey string) string {
// TODO: Multiple realms with the same pkey will be randomly chosen. // TODO: Multiple realms with the same pkey will be randomly chosen.
for r, realmConfig := range s.Rooms[roomID].Realms { for r, realmConfig := range s.Rooms[roomID].Realms {
for pkey, projectConfig := range realmConfig.Projects { for pkey, projectConfig := range realmConfig.Projects {
@ -324,7 +327,7 @@ func (s *Service) realmIDForProject(roomID, projectKey string) string {
return "" return ""
} }
func (s *Service) projectToRealm(userID, pkey string) (*jira.Realm, error) {
func (s *Service) projectToRealm(userID id.UserID, pkey string) (*jira.Realm, error) {
// We don't know which JIRA installation this project maps to, so: // We don't know which JIRA installation this project maps to, so:
// - Get all known JIRA realms and f.e query their endpoints with the // - Get all known JIRA realms and f.e query their endpoints with the
// given user ID's credentials (so if it is a private project they // given user ID's credentials (so if it is a private project they
@ -447,7 +450,7 @@ func htmlForEvent(whe *webhook.Event, jiraBaseURL string) string {
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
webhookEndpointURL: webhookEndpointURL, webhookEndpointURL: webhookEndpointURL,

7
services/jira/webhook/webhook.go

@ -11,6 +11,7 @@ import (
"github.com/matrix-org/go-neb/realms/jira" "github.com/matrix-org/go-neb/realms/jira"
"github.com/matrix-org/util" "github.com/matrix-org/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix/id"
) )
type jiraWebhook struct { type jiraWebhook struct {
@ -32,7 +33,7 @@ type Event struct {
} }
// RegisterHook checks to see if this user is allowed to track the given projects and then tracks them. // RegisterHook checks to see if this user is allowed to track the given projects and then tracks them.
func RegisterHook(jrealm *jira.Realm, projects []string, userID, webhookEndpointURL string) error {
func RegisterHook(jrealm *jira.Realm, projects []string, userID id.UserID, webhookEndpointURL string) error {
// Tracking means that a webhook may need to be created on the remote JIRA installation. // Tracking means that a webhook may need to be created on the remote JIRA installation.
// We need to make sure that the user has permission to do this. If they don't, it may still be okay if // We need to make sure that the user has permission to do this. If they don't, it may still be okay if
// there is an existing webhook set up for this installation by someone else, *PROVIDED* that the projects // there is an existing webhook set up for this installation by someone else, *PROVIDED* that the projects
@ -120,7 +121,7 @@ func OnReceiveRequest(req *http.Request) (string, *Event, *util.JSONResponse) {
return projKey, &whe, nil return projKey, &whe, nil
} }
func createWebhook(jrealm *jira.Realm, webhookEndpointURL, userID string) error {
func createWebhook(jrealm *jira.Realm, webhookEndpointURL string, userID id.UserID) error {
cli, err := jrealm.JIRAClient(userID, false) cli, err := jrealm.JIRAClient(userID, false)
if err != nil { if err != nil {
return err return err
@ -182,7 +183,7 @@ func getWebhook(cli *gojira.Client, webhookEndpointURL string) (*jiraWebhook, bo
return nebWH, false, nil return nebWH, false, nil
} }
func checkProjectsArePublic(jrealm *jira.Realm, projects []string, userID string) error {
func checkProjectsArePublic(jrealm *jira.Realm, projects []string, userID id.UserID) error {
publicCli, err := jrealm.JIRAClient("", true) publicCli, err := jrealm.JIRAClient("", true)
if err != nil { if err != nil {
return fmt.Errorf("Cannot create public JIRA client") return fmt.Errorf("Cannot create public JIRA client")

28
services/rssbot/rssbot.go

@ -16,10 +16,12 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/polling" "github.com/matrix-org/go-neb/polling"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"github.com/mmcdole/gofeed" "github.com/mmcdole/gofeed"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the RSS Bot service // ServiceType of the RSS Bot service
@ -81,7 +83,7 @@ type Service struct {
// Optional. The time to wait between polls. If this is less than minPollingIntervalSeconds, it is ignored. // Optional. The time to wait between polls. If this is less than minPollingIntervalSeconds, it is ignored.
PollIntervalMins int `json:"poll_interval_mins"` PollIntervalMins int `json:"poll_interval_mins"`
// The list of rooms to send feed updates into. This cannot be empty. // The list of rooms to send feed updates into. This cannot be empty.
Rooms []string `json:"rooms"`
Rooms []id.RoomID `json:"rooms"`
// True if rss bot is unable to poll this feed. This is populated by Go-NEB. Use /getService to // True if rss bot is unable to poll this feed. This is populated by Go-NEB. Use /getService to
// retrieve this value. // retrieve this value.
IsFailing bool `json:"is_failing"` IsFailing bool `json:"is_failing"`
@ -100,7 +102,7 @@ type Service struct {
} }
// Register will check the liveness of each RSS feed given. If all feeds check out okay, no error is returned. // Register will check the liveness of each RSS feed given. If all feeds check out okay, no error is returned.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
if len(s.Feeds) == 0 { if len(s.Feeds) == 0 {
// this is an error UNLESS the old service had some feeds in which case they are deleting us :( // this is an error UNLESS the old service had some feeds in which case they are deleting us :(
var numOldFeeds int var numOldFeeds int
@ -129,8 +131,8 @@ func (s *Service) Register(oldService types.Service, client *gomatrix.Client) er
return nil return nil
} }
func (s *Service) joinRooms(client *gomatrix.Client) {
roomSet := make(map[string]bool)
func (s *Service) joinRooms(client *mautrix.Client) {
roomSet := make(map[id.RoomID]bool)
for _, feedInfo := range s.Feeds { for _, feedInfo := range s.Feeds {
for _, roomID := range feedInfo.Rooms { for _, roomID := range feedInfo.Rooms {
roomSet[roomID] = true roomSet[roomID] = true
@ -138,7 +140,7 @@ func (s *Service) joinRooms(client *gomatrix.Client) {
} }
for roomID := range roomSet { for roomID := range roomSet {
if _, err := client.JoinRoom(roomID, "", nil); err != nil {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
"room_id": roomID, "room_id": roomID,
@ -173,7 +175,7 @@ func (s *Service) PostRegister(oldService types.Service) {
// - Else if there is a Title field, use it as the GUID. // - Else if there is a Title field, use it as the GUID.
// //
// Returns a timestamp representing when this Service should have OnPoll called again. // Returns a timestamp representing when this Service should have OnPoll called again.
func (s *Service) OnPoll(cli *gomatrix.Client) time.Time {
func (s *Service) OnPoll(cli *mautrix.Client) time.Time {
logger := log.WithFields(log.Fields{ logger := log.WithFields(log.Fields{
"service_id": s.ServiceID(), "service_id": s.ServiceID(),
"service_type": s.ServiceType(), "service_type": s.ServiceType(),
@ -406,7 +408,7 @@ func (s *Service) newItems(feedURL string, allItems []*gofeed.Item) (items []gof
return return
} }
func (s *Service) sendToRooms(cli *gomatrix.Client, feedURL string, feed *gofeed.Feed, item gofeed.Item) error {
func (s *Service) sendToRooms(cli *mautrix.Client, feedURL string, feed *gofeed.Feed, item gofeed.Item) error {
logger := log.WithFields(log.Fields{ logger := log.WithFields(log.Fields{
"feed_url": feedURL, "feed_url": feedURL,
"title": item.Title, "title": item.Title,
@ -414,14 +416,14 @@ func (s *Service) sendToRooms(cli *gomatrix.Client, feedURL string, feed *gofeed
}) })
logger.Info("Sending new feed item") logger.Info("Sending new feed item")
for _, roomID := range s.Feeds[feedURL].Rooms { for _, roomID := range s.Feeds[feedURL].Rooms {
if _, err := cli.SendMessageEvent(roomID, "m.room.message", itemToHTML(feed, item)); err != nil {
if _, err := cli.SendMessageEvent(roomID, mevt.EventMessage, itemToHTML(feed, item)); err != nil {
logger.WithError(err).WithField("room_id", roomID).Error("Failed to send to room") logger.WithError(err).WithField("room_id", roomID).Error("Failed to send to room")
} }
} }
return nil return nil
} }
func itemToHTML(feed *gofeed.Feed, item gofeed.Item) gomatrix.HTMLMessage {
func itemToHTML(feed *gofeed.Feed, item gofeed.Item) mevt.MessageEventContent {
// If an item does not have a title, try using the feed's title instead // If an item does not have a title, try using the feed's title instead
// Create a new variable instead of mutating that which is passed in // Create a new variable instead of mutating that which is passed in
itemTitle := item.Title itemTitle := item.Title
@ -442,11 +444,11 @@ func itemToHTML(feed *gofeed.Feed, item gofeed.Item) gomatrix.HTMLMessage {
html.EscapeString(item.Author.Email)) html.EscapeString(item.Author.Email))
} }
} }
return gomatrix.HTMLMessage{
return mevt.MessageEventContent{
Body: fmt.Sprintf("%s: %s ( %s )", Body: fmt.Sprintf("%s: %s ( %s )",
html.EscapeString(feed.Title), html.EscapeString(itemTitle), html.EscapeString(item.Link)), html.EscapeString(feed.Title), html.EscapeString(itemTitle), html.EscapeString(item.Link)),
MsgType: "m.notice", MsgType: "m.notice",
Format: "org.matrix.custom.html",
Format: mevt.FormatHTML,
FormattedBody: fmtBody, FormattedBody: fmtBody,
// <strong>FeedTitle</strong>: // <strong>FeedTitle</strong>:
// <br> // <br>
@ -532,7 +534,7 @@ func init() {
cachingClient = &http.Client{ cachingClient = &http.Client{
Transport: userAgentRoundTripper{httpcache.NewTransport(lruCache)}, Transport: userAgentRoundTripper{httpcache.NewTransport(lruCache)},
} }
types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
types.RegisterService(func(serviceID string, serviceUserID id.UserID, webhookEndpointURL string) types.Service {
r := &Service{ r := &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

10
services/rssbot/rssbot_test.go

@ -14,7 +14,9 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
const rssFeedXML = ` const rssFeedXML = `
@ -65,7 +67,7 @@ func createRSSClient(t *testing.T, feedURL string) *Service {
// Configure the service to force OnPoll to query the RSS feed and attempt to send results // Configure the service to force OnPoll to query the RSS feed and attempt to send results
// to the right room. // to the right room.
f := rssbot.Feeds[feedURL] f := rssbot.Feeds[feedURL]
f.Rooms = []string{"!linksroom:hyrule"}
f.Rooms = []id.RoomID{"!linksroom:hyrule"}
f.NextPollTimestampSecs = time.Now().Unix() f.NextPollTimestampSecs = time.Now().Unix()
rssbot.Feeds[feedURL] = f rssbot.Feeds[feedURL] = f
@ -84,7 +86,7 @@ func TestHTMLEntities(t *testing.T) {
matrixTrans.RT = func(req *http.Request) (*http.Response, error) { matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/_matrix/client/r0/rooms/!linksroom:hyrule/send/m.room.message") { if strings.HasPrefix(req.URL.Path, "/_matrix/client/r0/rooms/!linksroom:hyrule/send/m.room.message") {
// Check content body to make sure it is decoded // Check content body to make sure it is decoded
var msg gomatrix.HTMLMessage
var msg mevt.MessageEventContent
if err := json.NewDecoder(req.Body).Decode(&msg); err != nil { if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
t.Fatal("Failed to decode request JSON: ", err) t.Fatal("Failed to decode request JSON: ", err)
return nil, errors.New("Error handling matrix client test request") return nil, errors.New("Error handling matrix client test request")
@ -104,7 +106,7 @@ func TestHTMLEntities(t *testing.T) {
} }
return nil, errors.New("Unhandled matrix client test request") return nil, errors.New("Unhandled matrix client test request")
} }
matrixClient, _ := gomatrix.NewClient("https://hyrule", "@happy_mask_salesman:hyrule", "its_a_secret")
matrixClient, _ := mautrix.NewClient("https://hyrule", "@happy_mask_salesman:hyrule", "its_a_secret")
matrixClient.Client = &http.Client{Transport: matrixTrans} matrixClient.Client = &http.Client{Transport: matrixTrans}
// Invoke OnPoll to trigger the RSS feed update // Invoke OnPoll to trigger the RSS feed update

4
services/slackapi/message.go

@ -12,9 +12,9 @@ import (
"regexp" "regexp"
"time" "time"
"github.com/matrix-org/gomatrix"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
mevt "maunium.net/go/mautrix/event"
) )
type slackAttachment struct { type slackAttachment struct {
@ -195,7 +195,7 @@ func renderSlackAttachment(attachment *slackAttachment) {
} }
} }
func slackMessageToHTMLMessage(message slackMessage) (html gomatrix.HTMLMessage, err error) {
func slackMessageToHTMLMessage(message slackMessage) (html mevt.MessageEventContent, err error) {
text := linkifyString(message.Text) text := linkifyString(message.Text)
if message.Mrkdwn == nil || *message.Mrkdwn == true { if message.Mrkdwn == nil || *message.Mrkdwn == true {
message.TextRendered = template.HTML(blackfriday.MarkdownBasic([]byte(text))) message.TextRendered = template.HTML(blackfriday.MarkdownBasic([]byte(text)))

20
services/slackapi/slackapi.go

@ -5,8 +5,10 @@ import (
"strings" "strings"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Slack API service // ServiceType of the Slack API service
@ -27,15 +29,15 @@ type Service struct {
webhookEndpointURL string webhookEndpointURL string
// The URL which should be given to an outgoing slack webhook - Populated by Go-NEB after Service registration. // The URL which should be given to an outgoing slack webhook - Populated by Go-NEB after Service registration.
WebhookURL string `json:"webhook_url"` WebhookURL string `json:"webhook_url"`
RoomID string `json:"room_id"`
MessageType string `json:"message_type"`
RoomID id.RoomID `json:"room_id"`
MessageType event.MessageType `json:"message_type"`
} }
// OnReceiveWebhook receives requests from a slack outgoing webhook and possibly sends requests // OnReceiveWebhook receives requests from a slack outgoing webhook and possibly sends requests
// to Matrix as a result. // to Matrix as a result.
// //
// This requires that the WebhookURL is given to an outgoing slack webhook (see https://api.slack.com/outgoing-webhooks) // This requires that the WebhookURL is given to an outgoing slack webhook (see https://api.slack.com/outgoing-webhooks)
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
segments := strings.Split(req.URL.Path, "/") segments := strings.Split(req.URL.Path, "/")
if len(segments) < 2 { if len(segments) < 2 {
@ -45,7 +47,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
messageType := s.MessageType messageType := s.MessageType
if messageType == "" { if messageType == "" {
messageType = "m.text"
messageType = event.MsgText
} }
roomID := s.RoomID roomID := s.RoomID
@ -64,15 +66,15 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
} }
htmlMessage.MsgType = messageType htmlMessage.MsgType = messageType
cli.SendMessageEvent( cli.SendMessageEvent(
roomID, "m.room.message", htmlMessage,
roomID, event.EventMessage, htmlMessage,
) )
w.WriteHeader(200) w.WriteHeader(200)
} }
// Register joins the configured room and sets the public WebhookURL // Register joins the configured room and sets the public WebhookURL
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
s.WebhookURL = s.webhookEndpointURL s.WebhookURL = s.webhookEndpointURL
if _, err := client.JoinRoom(s.RoomID, "", nil); err != nil {
if _, err := client.JoinRoom(s.RoomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
"room_id": s.RoomID, "room_id": s.RoomID,
@ -83,7 +85,7 @@ func (s *Service) Register(oldService types.Service, client *gomatrix.Client) er
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
webhookEndpointURL: webhookEndpointURL, webhookEndpointURL: webhookEndpointURL,

20
services/travisci/travisci.go

@ -12,8 +12,10 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Travis-CI service. // ServiceType of the Travis-CI service.
@ -54,7 +56,7 @@ type Service struct {
// The URL which should be added to .travis.yml - Populated by Go-NEB after Service registration. // The URL which should be added to .travis.yml - Populated by Go-NEB after Service registration.
WebhookURL string `json:"webhook_url"` WebhookURL string `json:"webhook_url"`
// A map from Matrix room ID to Github-style owner/repo repositories. // A map from Matrix room ID to Github-style owner/repo repositories.
Rooms map[string]struct {
Rooms map[id.RoomID]struct {
// A map of "owner/repo" to configuration information // A map of "owner/repo" to configuration information
Repos map[string]struct { Repos map[string]struct {
// The template string to use when creating notifications. // The template string to use when creating notifications.
@ -178,7 +180,7 @@ func outputForTemplate(travisTmpl string, tmpl map[string]string) (out string) {
// webhooks: http://go-neb-endpoint.com/notifications // webhooks: http://go-neb-endpoint.com/notifications
// //
// See https://docs.travis-ci.com/user/notifications#Webhook-notifications for more information. // See https://docs.travis-ci.com/user/notifications#Webhook-notifications for more information.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
if err := req.ParseForm(); err != nil { if err := req.ParseForm(); err != nil {
log.WithError(err).Error("Failed to read incoming Travis-CI webhook form") log.WithError(err).Error("Failed to read incoming Travis-CI webhook form")
w.WriteHeader(400) w.WriteHeader(400)
@ -222,7 +224,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
if ownerRepo != whForRepo { if ownerRepo != whForRepo {
continue continue
} }
msg := gomatrix.TextMessage{
msg := mevt.MessageEventContent{
Body: outputForTemplate(repoData.Template, tmplData), Body: outputForTemplate(repoData.Template, tmplData),
MsgType: "m.notice", MsgType: "m.notice",
} }
@ -231,7 +233,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
"message": msg, "message": msg,
"room_id": roomID, "room_id": roomID,
}).Print("Sending Travis-CI notification to room") }).Print("Sending Travis-CI notification to room")
if _, e := cli.SendMessageEvent(roomID, "m.room.message", msg); e != nil {
if _, e := cli.SendMessageEvent(roomID, mevt.EventMessage, msg); e != nil {
logger.WithError(e).WithField("room_id", roomID).Print( logger.WithError(e).WithField("room_id", roomID).Print(
"Failed to send Travis-CI notification to room.") "Failed to send Travis-CI notification to room.")
} }
@ -241,7 +243,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
} }
// Register makes sure the Config information supplied is valid. // Register makes sure the Config information supplied is valid.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
s.WebhookURL = s.webhookEndpointURL s.WebhookURL = s.webhookEndpointURL
for _, roomData := range s.Rooms { for _, roomData := range s.Rooms {
for repo := range roomData.Repos { for repo := range roomData.Repos {
@ -273,9 +275,9 @@ func (s *Service) PostRegister(oldService types.Service) {
} }
} }
func (s *Service) joinRooms(client *gomatrix.Client) {
func (s *Service) joinRooms(client *mautrix.Client) {
for roomID := range s.Rooms { for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID, "", nil); err != nil {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
log.ErrorKey: err, log.ErrorKey: err,
"room_id": roomID, "room_id": roomID,
@ -286,7 +288,7 @@ func (s *Service) joinRooms(client *gomatrix.Client) {
} }
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
webhookEndpointURL: webhookEndpointURL, webhookEndpointURL: webhookEndpointURL,

13
services/travisci/travisci_test.go

@ -13,7 +13,8 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
) )
const travisOrgPEMPublicKey = (`-----BEGIN PUBLIC KEY----- const travisOrgPEMPublicKey = (`-----BEGIN PUBLIC KEY-----
@ -114,13 +115,13 @@ func TestTravisCI(t *testing.T) {
httpClient = &http.Client{Transport: travisTransport} httpClient = &http.Client{Transport: travisTransport}
// Intercept message sending to Matrix and mock responses // Intercept message sending to Matrix and mock responses
msgs := []gomatrix.TextMessage{}
msgs := []mevt.MessageEventContent{}
matrixTrans := struct{ testutils.MockTransport }{} matrixTrans := struct{ testutils.MockTransport }{}
matrixTrans.RT = func(req *http.Request) (*http.Response, error) { matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if !strings.Contains(req.URL.String(), "/send/m.room.message") { if !strings.Contains(req.URL.String(), "/send/m.room.message") {
return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String()) 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 { if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
return nil, fmt.Errorf("Failed to decode request JSON: %s", err) return nil, fmt.Errorf("Failed to decode request JSON: %s", err)
} }
@ -130,13 +131,13 @@ func TestTravisCI(t *testing.T) {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)),
}, nil }, nil
} }
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@travisci:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@travisci:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
// BEGIN running the Travis-CI table tests // BEGIN running the Travis-CI table tests
// --------------------------------------- // ---------------------------------------
for _, test := range travisTests { for _, test := range travisTests {
msgs = []gomatrix.TextMessage{} // reset sent messages
msgs = []mevt.MessageEventContent{} // reset sent messages
mockWriter := httptest.NewRecorder() mockWriter := httptest.NewRecorder()
travis := makeService(t, test.Template) travis := makeService(t, test.Template)
if travis == nil { if travis == nil {
@ -172,7 +173,7 @@ func TestTravisCI(t *testing.T) {
} }
} }
func assertResponse(t *testing.T, w *httptest.ResponseRecorder, msgs []gomatrix.TextMessage, expectCode int, expectMsgLength int) bool {
func assertResponse(t *testing.T, w *httptest.ResponseRecorder, msgs []mevt.MessageEventContent, expectCode int, expectMsgLength int) bool {
if w.Code != expectCode { if w.Code != expectCode {
t.Errorf("TestTravisCI OnReceiveWebhook want HTTP code %d, got %d", expectCode, w.Code) t.Errorf("TestTravisCI OnReceiveWebhook want HTTP code %d, got %d", expectCode, w.Code)
return false return false

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)
}
}

28
services/wikipedia/wikipedia.go

@ -11,8 +11,10 @@ import (
"github.com/jaytaylor/html2text" "github.com/jaytaylor/html2text"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
) )
// ServiceType of the Wikipedia service // ServiceType of the Wikipedia service
@ -49,11 +51,11 @@ type Service struct {
// Commands supported: // Commands supported:
// !wikipedia some_search_query_without_quotes // !wikipedia some_search_query_without_quotes
// Responds with a suitable article extract and link to the referenced page into the same room as the command. // Responds with a suitable article extract and link to the referenced page into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
func (s *Service) Commands(client *mautrix.Client) []types.Command {
return []types.Command{ return []types.Command{
types.Command{
{
Path: []string{"wikipedia"}, Path: []string{"wikipedia"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return s.cmdWikipediaSearch(client, roomID, userID, args) return s.cmdWikipediaSearch(client, roomID, userID, args)
}, },
}, },
@ -61,12 +63,14 @@ func (s *Service) Commands(client *gomatrix.Client) []types.Command {
} }
// usageMessage returns a matrix TextMessage representation of the service usage // usageMessage returns a matrix TextMessage representation of the service usage
func usageMessage() *gomatrix.TextMessage {
return &gomatrix.TextMessage{"m.notice",
`Usage: !wikipedia search_text`}
func usageMessage() *mevt.MessageEventContent {
return &mevt.MessageEventContent{
MsgType: mevt.MsgNotice,
Body: "Usage: !wikipedia search_text",
}
} }
func (s *Service) cmdWikipediaSearch(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {
func (s *Service) cmdWikipediaSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// Check for query text // Check for query text
if len(args) < 1 { if len(args) < 1 {
return usageMessage(), nil return usageMessage(), nil
@ -81,7 +85,7 @@ func (s *Service) cmdWikipediaSearch(client *gomatrix.Client, roomID, userID str
// No article extracts // No article extracts
if searchResultPage == nil || searchResultPage.Extract == "" { if searchResultPage == nil || searchResultPage.Extract == "" {
return gomatrix.TextMessage{
return mevt.MessageEventContent{
MsgType: "m.notice", MsgType: "m.notice",
Body: "No results", Body: "No results",
}, nil }, nil
@ -90,7 +94,7 @@ func (s *Service) cmdWikipediaSearch(client *gomatrix.Client, roomID, userID str
// Convert article HTML to text // Convert article HTML to text
extractText, err := html2text.FromString(searchResultPage.Extract) extractText, err := html2text.FromString(searchResultPage.Extract)
if err != nil { if err != nil {
return gomatrix.TextMessage{
return mevt.MessageEventContent{
MsgType: "m.notice", MsgType: "m.notice",
Body: "Failed to convert extract to plain text - " + err.Error(), Body: "Failed to convert extract to plain text - " + err.Error(),
}, nil }, nil
@ -105,7 +109,7 @@ func (s *Service) cmdWikipediaSearch(client *gomatrix.Client, roomID, userID str
extractText += fmt.Sprintf("\nhttp://en.wikipedia.org/?curid=%d", searchResultPage.PageID) extractText += fmt.Sprintf("\nhttp://en.wikipedia.org/?curid=%d", searchResultPage.PageID)
// Return article extract // Return article extract
return gomatrix.TextMessage{
return mevt.MessageEventContent{
MsgType: "m.notice", MsgType: "m.notice",
Body: extractText, Body: extractText,
}, nil }, nil
@ -175,7 +179,7 @@ func response2String(res *http.Response) string {
// Initialise the service // Initialise the service
func init() { 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{ return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
} }

4
services/wikipedia/wikipedia_test.go

@ -12,7 +12,7 @@ import (
"github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/testutils" "github.com/matrix-org/go-neb/testutils"
"github.com/matrix-org/go-neb/types" "github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
) )
// TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make // TODO: It would be nice to tabularise this test so we can try failing different combinations of responses to make
@ -83,7 +83,7 @@ func TestCommand(t *testing.T) {
matrixTrans.RT = func(req *http.Request) (*http.Response, error) { matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("Unknown URL: %s", req.URL.String()) return nil, fmt.Errorf("Unknown URL: %s", req.URL.String())
} }
matrixCli, _ := gomatrix.NewClient("https://hyrule", "@wikipediabot:hyrule", "its_a_secret")
matrixCli, _ := mautrix.NewClient("https://hyrule", "@wikipediabot:hyrule", "its_a_secret")
matrixCli.Client = &http.Client{Transport: matrixTrans} matrixCli.Client = &http.Client{Transport: matrixTrans}
// Execute the matrix !command // Execute the matrix !command

6
testutil_test.go

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"maunium.net/go/mautrix/id"
) )
// newResponse creates a new HTTP response with the given data. // newResponse creates a new HTTP response with the given data.
@ -46,8 +48,8 @@ func (rt *matrixTripper) Handle(method, path string, handler func(req *http.Requ
rt.handlers[key] = handler rt.handlers[key] = handler
} }
func (rt *matrixTripper) HandlePOSTFilter(userID string) {
rt.Handle("POST", "/_matrix/client/r0/user/"+userID+"/filter",
func (rt *matrixTripper) HandlePOSTFilter(userID id.UserID) {
rt.Handle("POST", "/_matrix/client/r0/user/"+userID.String()+"/filter",
func(req *http.Request) (*http.Response, error) { func(req *http.Request) (*http.Response, error) {
return newResponse(200, `{ return newResponse(200, `{
"filter_id":"abcdef" "filter_id":"abcdef"

6
types/actions.go

@ -3,6 +3,8 @@ package types
import ( import (
"regexp" "regexp"
"strings" "strings"
"maunium.net/go/mautrix/id"
) )
// A Command is something that a user invokes by sending a message starting with '!' // A Command is something that a user invokes by sending a message starting with '!'
@ -13,7 +15,7 @@ type Command struct {
Path []string Path []string
Arguments []string Arguments []string
Help string Help string
Command func(roomID, userID string, arguments []string) (content interface{}, err error)
Command func(roomID id.RoomID, userID id.UserID, arguments []string) (content interface{}, err error)
} }
// An Expansion is something that actives when the user sends any message // An Expansion is something that actives when the user sends any message
@ -22,7 +24,7 @@ type Command struct {
// the appropriate RFC. // the appropriate RFC.
type Expansion struct { type Expansion struct {
Regexp *regexp.Regexp Regexp *regexp.Regexp
Expand func(roomID, userID string, matchingGroups []string) interface{}
Expand func(roomID id.RoomID, userID id.UserID, matchingGroups []string) interface{}
} }
// Matches if the arguments start with the path of the command. // Matches if the arguments start with the path of the command.

8
types/auth.go

@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
"maunium.net/go/mautrix/id"
) )
// AuthRealm represents a place where a user can authenticate themselves. // AuthRealm represents a place where a user can authenticate themselves.
@ -15,8 +17,8 @@ type AuthRealm interface {
Init() error Init() error
Register() error Register() error
OnReceiveRedirect(w http.ResponseWriter, req *http.Request) OnReceiveRedirect(w http.ResponseWriter, req *http.Request)
AuthSession(id, userID, realmID string) AuthSession
RequestAuthSession(userID string, config json.RawMessage) interface{}
AuthSession(id string, userID id.UserID, realmID string) AuthSession
RequestAuthSession(userID id.UserID, config json.RawMessage) interface{}
} }
var realmsByType = map[string]func(string, string) AuthRealm{} var realmsByType = map[string]func(string, string) AuthRealm{}
@ -49,7 +51,7 @@ func CreateAuthRealm(realmID, realmType string, realmJSON []byte) (AuthRealm, er
// an auth realm. // an auth realm.
type AuthSession interface { type AuthSession interface {
ID() string ID() string
UserID() string
UserID() id.UserID
RealmID() string RealmID() string
Authenticated() bool Authenticated() bool
Info() interface{} Info() interface{}

41
types/service.go

@ -8,14 +8,15 @@ import (
"strings" "strings"
"time" "time"
"github.com/matrix-org/gomatrix"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
) )
// BotOptions for a given bot user in a given room // BotOptions for a given bot user in a given room
type BotOptions struct { type BotOptions struct {
RoomID string
UserID string
SetByUserID string
RoomID id.RoomID
UserID id.UserID
SetByUserID id.UserID
Options map[string]interface{} Options map[string]interface{}
} }
@ -23,25 +24,25 @@ type BotOptions struct {
type Poller interface { type Poller interface {
// OnPoll is called when the poller should poll. Return the timestamp when you want to be polled again. // OnPoll is called when the poller should poll. Return the timestamp when you want to be polled again.
// Return 0 to never be polled again. // Return 0 to never be polled again.
OnPoll(client *gomatrix.Client) time.Time
OnPoll(client *mautrix.Client) time.Time
} }
// A Service is the configuration for a bot service. // A Service is the configuration for a bot service.
type Service interface { type Service interface {
// Return the user ID of this service. // Return the user ID of this service.
ServiceUserID() string
ServiceUserID() id.UserID
// Return an opaque ID used to identify this service. // Return an opaque ID used to identify this service.
ServiceID() string ServiceID() string
// Return the type of service. This string MUST NOT change. // Return the type of service. This string MUST NOT change.
ServiceType() string ServiceType() string
Commands(cli *gomatrix.Client) []Command
Expansions(cli *gomatrix.Client) []Expansion
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client)
Commands(cli *mautrix.Client) []Command
Expansions(cli *mautrix.Client) []Expansion
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client)
// A lifecycle function which is invoked when the service is being registered. The old service, if one exists, is provided, // A lifecycle function which is invoked when the service is being registered. The old service, if one exists, is provided,
// along with a Client instance for ServiceUserID(). If this function returns an error, the service will not be registered // along with a Client instance for ServiceUserID(). If this function returns an error, the service will not be registered
// or persisted to the database, and the user's request will fail. This can be useful if you depend on external factors // or persisted to the database, and the user's request will fail. This can be useful if you depend on external factors
// such as registering webhooks. // such as registering webhooks.
Register(oldService Service, client *gomatrix.Client) error
Register(oldService Service, client *mautrix.Client) error
// A lifecycle function which is invoked after the service has been successfully registered and persisted to the database. // A lifecycle function which is invoked after the service has been successfully registered and persisted to the database.
// This function is invoked within the critical section for configuring services, guaranteeing that there will not be // This function is invoked within the critical section for configuring services, guaranteeing that there will not be
// concurrent modifications to this service whilst this function executes. This lifecycle hook should be used to clean // concurrent modifications to this service whilst this function executes. This lifecycle hook should be used to clean
@ -52,12 +53,12 @@ type Service interface {
// DefaultService NO-OPs the implementation of optional Service interface methods. Feel free to override them. // DefaultService NO-OPs the implementation of optional Service interface methods. Feel free to override them.
type DefaultService struct { type DefaultService struct {
id string id string
serviceUserID string
serviceUserID id.UserID
serviceType string serviceType string
} }
// NewDefaultService creates a new service with implementations for ServiceID(), ServiceType() and ServiceUserID() // NewDefaultService creates a new service with implementations for ServiceID(), ServiceType() and ServiceUserID()
func NewDefaultService(serviceID, serviceUserID, serviceType string) DefaultService {
func NewDefaultService(serviceID string, serviceUserID id.UserID, serviceType string) DefaultService {
return DefaultService{serviceID, serviceUserID, serviceType} return DefaultService{serviceID, serviceUserID, serviceType}
} }
@ -70,7 +71,7 @@ func (s *DefaultService) ServiceID() string {
// ServiceUserID returns the user ID that the service sends events as. In order for this to return the // ServiceUserID returns the user ID that the service sends events as. In order for this to return the
// service user ID, DefaultService MUST have been initialised by NewDefaultService, the zero-initialiser // service user ID, DefaultService MUST have been initialised by NewDefaultService, the zero-initialiser
// is NOT enough. // is NOT enough.
func (s *DefaultService) ServiceUserID() string {
func (s *DefaultService) ServiceUserID() id.UserID {
return s.serviceUserID return s.serviceUserID
} }
@ -82,23 +83,23 @@ func (s *DefaultService) ServiceType() string {
} }
// Commands returns no commands. // Commands returns no commands.
func (s *DefaultService) Commands(cli *gomatrix.Client) []Command {
func (s *DefaultService) Commands(cli *mautrix.Client) []Command {
return []Command{} return []Command{}
} }
// Expansions returns no expansions. // Expansions returns no expansions.
func (s *DefaultService) Expansions(cli *gomatrix.Client) []Expansion {
func (s *DefaultService) Expansions(cli *mautrix.Client) []Expansion {
return []Expansion{} return []Expansion{}
} }
// Register does nothing and returns no error. // Register does nothing and returns no error.
func (s *DefaultService) Register(oldService Service, client *gomatrix.Client) error { return nil }
func (s *DefaultService) Register(oldService Service, client *mautrix.Client) error { return nil }
// PostRegister does nothing. // PostRegister does nothing.
func (s *DefaultService) PostRegister(oldService Service) {} func (s *DefaultService) PostRegister(oldService Service) {}
// OnReceiveWebhook does nothing but 200 OK the request. // OnReceiveWebhook does nothing but 200 OK the request.
func (s *DefaultService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
func (s *DefaultService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
w.WriteHeader(200) // Do nothing w.WriteHeader(200) // Do nothing
} }
@ -120,11 +121,11 @@ func BaseURL(u string) error {
return nil return nil
} }
var servicesByType = map[string]func(string, string, string) Service{}
var servicesByType = map[string]func(string, id.UserID, string) Service{}
var serviceTypesWhichPoll = map[string]bool{} var serviceTypesWhichPoll = map[string]bool{}
// RegisterService registers a factory for creating Service instances. // RegisterService registers a factory for creating Service instances.
func RegisterService(factory func(string, string, string) Service) {
func RegisterService(factory func(string, id.UserID, string) Service) {
s := factory("", "", "") s := factory("", "", "")
servicesByType[s.ServiceType()] = factory servicesByType[s.ServiceType()] = factory
@ -143,7 +144,7 @@ func PollingServiceTypes() (types []string) {
// CreateService creates a Service of the given type and serviceID. // CreateService creates a Service of the given type and serviceID.
// Returns an error if the Service couldn't be created. // Returns an error if the Service couldn't be created.
func CreateService(serviceID, serviceType, serviceUserID string, serviceJSON []byte) (Service, error) {
func CreateService(serviceID, serviceType string, serviceUserID id.UserID, serviceJSON []byte) (Service, error) {
f := servicesByType[serviceType] f := servicesByType[serviceType]
if f == nil { if f == nil {
return nil, errors.New("Unknown service type: " + serviceType) return nil, errors.New("Unknown service type: " + serviceType)

Loading…
Cancel
Save