Browse Source

Enable e2ee across all services and save crypto material in the database (#324)

* Add device ID to the configuration

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

* Basic e2ee support for some commands

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

* Move some of the client and crypto logic to a new BotClient type

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

* Use the state store to retrieve room joined users

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

* Start creating the database APIs for the crypto store

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

* Replace mautrix.Client usage with BotClient for all services to use the
e2ee-enabled client

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

* Use SQL backend for storing crypto material

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

* Perform a sync request with full state when starting

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

* Consider case where device ID is empty and log a warning

Signed-off-by: Nikos Filippakis <me@nfil.dev>
pull/327/head
Nikos Filippakis 5 years ago
committed by GitHub
parent
commit
1be6c8c1cd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      api/api.go
  2. 3
      api/handlers/service.go
  3. 143
      clients/bot_client.go
  4. 146
      clients/clients.go
  5. 5
      clients/clients_test.go
  6. 28
      clients/crypto_logger.go
  7. 79
      clients/state_store.go
  8. 8
      database/db.go
  9. 9
      go.mod
  10. 17
      go.sum
  11. 2
      goneb.go
  12. 8
      services/alertmanager/alertmanager.go
  13. 2
      services/alertmanager/alertmanager_test.go
  14. 5
      services/echo/echo.go
  15. 5
      services/giphy/giphy.go
  16. 7
      services/github/github.go
  17. 7
      services/github/github_webhook.go
  18. 6
      services/google/google.go
  19. 5
      services/guggy/guggy.go
  20. 5
      services/imgur/imgur.go
  21. 11
      services/jira/jira.go
  22. 10
      services/rssbot/rssbot.go
  23. 6
      services/slackapi/slackapi.go
  24. 8
      services/travisci/travisci.go
  25. 5
      services/wikipedia/wikipedia.go
  26. 30
      types/service.go

2
api/api.go

@ -63,6 +63,8 @@ type ClientConfig struct {
HomeserverURL string
// The matrix access token to authenticate the requests with.
AccessToken string
// The device ID for this access token.
DeviceID id.DeviceID
// True to start a sync stream for this user, making this a "syncing client". If false, no
// /sync goroutine will be created and this client won't listen for new events from Matrix. For services
// which only SEND events into Matrix, it may be desirable to set Sync to false to reduce the

3
api/handlers/service.go

@ -16,7 +16,6 @@ import (
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/util"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
)
// ConfigureService represents an HTTP handler which can process /admin/configureService requests.
@ -225,7 +224,7 @@ func (h *GetService) OnIncomingRequest(req *http.Request) util.JSONResponse {
}
}
func checkClientForService(service types.Service, client *mautrix.Client) error {
func checkClientForService(service types.Service, client *clients.BotClient) error {
// 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!
cmds := service.Commands(client)

143
clients/bot_client.go

@ -0,0 +1,143 @@
package clients
import (
"time"
"github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// BotClient represents one of the bot's sessions, with a specific User and Device ID.
// It can be used for sending messages and retrieving information about the rooms that
// the client has joined.
type BotClient struct {
*mautrix.Client
config api.ClientConfig
olmMachine *crypto.OlmMachine
stateStore *NebStateStore
}
// InitOlmMachine initializes a BotClient's internal OlmMachine given a client object and a Neb store,
// which will be used to store room information.
func (botClient *BotClient) InitOlmMachine(client *mautrix.Client, nebStore *matrix.NEBStore) (err error) {
var cryptoStore crypto.Store
cryptoLogger := CryptoMachineLogger{}
if sdb, ok := database.GetServiceDB().(*database.ServiceDB); ok {
// Create an SQL crypto store based on the ServiceDB used
db, dialect := sdb.GetSQLDb()
sqlCryptoStore := crypto.NewSQLCryptoStore(db, dialect, client.DeviceID, []byte(client.DeviceID.String()), cryptoLogger)
// Try to create the tables if they are missing
if err = sqlCryptoStore.CreateTables(); err != nil {
return
}
cryptoStore = sqlCryptoStore
cryptoLogger.Debug("Using SQL backend as the crypto store")
} else {
deviceID := client.DeviceID.String()
if deviceID == "" {
deviceID = "_empty_device_id"
}
cryptoStore, err = crypto.NewGobStore(deviceID + ".gob")
if err != nil {
return
}
cryptoLogger.Debug("Using gob storage as the crypto store")
}
botClient.stateStore = &NebStateStore{&nebStore.InMemoryStore}
olmMachine := crypto.NewOlmMachine(client, cryptoLogger, cryptoStore, botClient.stateStore)
if err = olmMachine.Load(); err != nil {
return
}
botClient.olmMachine = olmMachine
return nil
}
// Register registers a BotClient's Sync and StateMember event callbacks to update its internal state
// when new events arrive.
func (botClient *BotClient) Register(syncer mautrix.ExtensibleSyncer) {
syncer.OnEventType(mevt.StateMember, func(_ mautrix.EventSource, evt *mevt.Event) {
botClient.olmMachine.HandleMemberEvent(evt)
})
syncer.OnSync(botClient.syncCallback)
}
func (botClient *BotClient) syncCallback(resp *mautrix.RespSync, since string) bool {
botClient.stateStore.UpdateStateStore(resp)
botClient.olmMachine.ProcessSyncResponse(resp, since)
if err := botClient.olmMachine.CryptoStore.Flush(); err != nil {
log.WithError(err).Error("Could not flush crypto store")
}
return true
}
// DecryptMegolmEvent attempts to decrypt an incoming m.room.encrypted message using the session information
// already present in the OlmMachine. The corresponding decrypted event is then returned.
// If it fails, usually because the session is not known, an error is returned.
func (botClient *BotClient) DecryptMegolmEvent(evt *mevt.Event) (*mevt.Event, error) {
return botClient.olmMachine.DecryptMegolmEvent(evt)
}
// SendMessageEvent sends the given content to the given room ID using this BotClient as a message event.
// If the target room has enabled encryption, a megolm session is created if one doesn't already exist
// and the message is sent after being encrypted.
func (botClient *BotClient) SendMessageEvent(roomID id.RoomID, evtType mevt.Type, content interface{},
extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) {
olmMachine := botClient.olmMachine
if olmMachine.StateStore.IsEncrypted(roomID) {
// Check if there is already a megolm session
if sess, err := olmMachine.CryptoStore.GetOutboundGroupSession(roomID); err != nil {
return nil, err
} else if sess == nil || sess.Expired() || !sess.Shared {
// No error but valid, shared session does not exist
memberIDs, err := botClient.stateStore.GetJoinedMembers(roomID)
if err != nil {
return nil, err
}
// Share group session with room members
if err = olmMachine.ShareGroupSession(roomID, memberIDs); err != nil {
return nil, err
}
}
enc, err := olmMachine.EncryptMegolmEvent(roomID, mevt.EventMessage, content)
if err != nil {
return nil, err
}
content = enc
evtType = mevt.EventEncrypted
}
return botClient.Client.SendMessageEvent(roomID, evtType, content, extra...)
}
// Sync loops to keep syncing the client with the homeserver by calling the /sync endpoint.
func (botClient *BotClient) Sync() {
// Get the state store up to date
resp, err := botClient.SyncRequest(30000, "", "", true, mevt.PresenceOnline)
if err != nil {
log.WithError(err).Error("Error performing initial sync")
return
}
botClient.stateStore.UpdateStateStore(resp)
for {
if e := botClient.Client.Sync(); e != nil {
log.WithFields(log.Fields{
log.ErrorKey: e,
"user_id": botClient.config.UserID,
}).Error("Fatal Sync() error")
time.Sleep(10 * time.Second)
} else {
log.WithField("user_id", botClient.config.UserID).Info("Stopping Sync()")
return
}
}
}

146
clients/clients.go

@ -6,7 +6,6 @@ import (
"net/http"
"strings"
"sync"
"time"
"github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/database"
@ -26,7 +25,7 @@ type Clients struct {
httpClient *http.Client
dbMutex sync.Mutex
mapMutex sync.Mutex
clients map[id.UserID]clientEntry
clients map[id.UserID]BotClient
}
// New makes a new collection of matrix clients
@ -34,19 +33,19 @@ func New(db database.Storer, cli *http.Client) *Clients {
clients := &Clients{
db: db,
httpClient: cli,
clients: make(map[id.UserID]clientEntry), // user_id => clientEntry
clients: make(map[id.UserID]BotClient), // user_id => BotClient
}
return clients
}
// Client gets a client for the userID
func (c *Clients) Client(userID id.UserID) (*mautrix.Client, error) {
func (c *Clients) Client(userID id.UserID) (*BotClient, error) {
entry := c.getClient(userID)
if entry.client != nil {
return entry.client, nil
if entry.Client != nil {
return &entry, nil
}
entry, err := c.loadClientFromDB(userID)
return entry.client, err
return &entry, err
}
// Update updates the config for a matrix client
@ -71,29 +70,24 @@ func (c *Clients) Start() error {
return nil
}
type clientEntry struct {
config api.ClientConfig
client *mautrix.Client
}
func (c *Clients) getClient(userID id.UserID) clientEntry {
func (c *Clients) getClient(userID id.UserID) BotClient {
c.mapMutex.Lock()
defer c.mapMutex.Unlock()
return c.clients[userID]
}
func (c *Clients) setClient(client clientEntry) {
func (c *Clients) setClient(client BotClient) {
c.mapMutex.Lock()
defer c.mapMutex.Unlock()
c.clients[client.config.UserID] = client
}
func (c *Clients) loadClientFromDB(userID id.UserID) (entry clientEntry, err error) {
func (c *Clients) loadClientFromDB(userID id.UserID) (entry BotClient, err error) {
c.dbMutex.Lock()
defer c.dbMutex.Unlock()
entry = c.getClient(userID)
if entry.client != nil {
if entry.Client != nil {
return
}
@ -104,7 +98,7 @@ func (c *Clients) loadClientFromDB(userID id.UserID) (entry clientEntry, err err
return
}
if entry.client, err = c.newClient(entry.config); err != nil {
if err = c.initClient(&entry); err != nil {
return
}
@ -112,12 +106,12 @@ func (c *Clients) loadClientFromDB(userID id.UserID) (entry clientEntry, err err
return
}
func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry, old clientEntry, err error) {
func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new, old BotClient, err error) {
c.dbMutex.Lock()
defer c.dbMutex.Unlock()
old = c.getClient(newConfig.UserID)
if old.client != nil && old.config == newConfig {
if old.Client != nil && old.config == newConfig {
// Already have a client with that config.
new = old
return
@ -125,13 +119,13 @@ func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry,
new.config = newConfig
if new.client, err = c.newClient(new.config); err != nil {
if err = c.initClient(&new); err != nil {
return
}
// set the new display name if they differ
if old.config.DisplayName != new.config.DisplayName {
if err := new.client.SetDisplayName(new.config.DisplayName); err != nil {
if err := new.SetDisplayName(new.config.DisplayName); err != nil {
// whine about it but don't stop: this isn't fatal.
log.WithFields(log.Fields{
log.ErrorKey: err,
@ -142,12 +136,12 @@ func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry,
}
if old.config, err = c.db.StoreMatrixClientConfig(new.config); err != nil {
new.client.StopSync()
new.StopSync()
return
}
if old.client != nil {
old.client.StopSync()
if old.Client != nil {
old.Client.StopSync()
return
}
@ -155,13 +149,13 @@ func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry,
return
}
func (c *Clients) onMessageEvent(client *mautrix.Client, event *mevt.Event) {
services, err := c.db.LoadServicesForUser(client.UserID)
func (c *Clients) onMessageEvent(botClient *BotClient, event *mevt.Event) {
services, err := c.db.LoadServicesForUser(botClient.UserID)
if err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": event.RoomID,
"service_user_id": client.UserID,
"service_user_id": botClient.UserID,
}).Warn("Error loading services")
}
@ -196,23 +190,22 @@ func (c *Clients) onMessageEvent(client *mautrix.Client, event *mevt.Event) {
args = strings.Split(body[1:], " ")
}
if response := runCommandForService(service.Commands(client), event, args); response != nil {
if response := runCommandForService(service.Commands(botClient), event, args); response != nil {
responses = append(responses, response)
}
} else { // message isn't a command, it might need expanding
expansions := runExpansionsForService(service.Expansions(client), event, body)
expansions := runExpansionsForService(service.Expansions(botClient), event, body)
responses = append(responses, expansions...)
}
}
for _, content := range responses {
if _, err := client.SendMessageEvent(event.RoomID, mevt.EventMessage, content); err != nil {
if _, err := botClient.SendMessageEvent(event.RoomID, mevt.EventMessage, content); err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": event.RoomID,
"user_id": event.Sender,
"content": content,
}).Print("Failed to send command response")
"sender": event.Sender,
}).WithError(err).Error("Failed to send command response")
}
}
}
@ -341,61 +334,102 @@ func (c *Clients) onRoomMemberEvent(client *mautrix.Client, event *mevt.Event) {
}
}
func (c *Clients) newClient(config api.ClientConfig) (*mautrix.Client, error) {
func (c *Clients) initClient(botClient *BotClient) error {
config := botClient.config
client, err := mautrix.NewClient(config.HomeserverURL, config.UserID, config.AccessToken)
if err != nil {
return nil, err
return err
}
client.Client = c.httpClient
client.DeviceID = config.DeviceID
if client.DeviceID == "" {
log.Warn("Device ID is not set which will result in E2E encryption/decryption not working")
}
botClient.Client = client
syncer := client.Syncer.(*mautrix.DefaultSyncer)
nebStore := &matrix.NEBStore{
InMemoryStore: *mautrix.NewInMemoryStore(),
Database: c.db,
ClientConfig: config,
}
client.Store = nebStore
syncer.Store = nebStore
// TODO: Check that the access token is valid for the userID by peforming
// a request against the server.
syncer.OnEventType(mevt.EventMessage, func(event *mevt.Event) {
c.onMessageEvent(client, event)
if err = botClient.InitOlmMachine(client, nebStore); err != nil {
return err
}
// Register sync callback for maintaining the state store and Olm machine state
botClient.Register(syncer)
syncer.OnEventType(mevt.EventMessage, func(_ mautrix.EventSource, event *mevt.Event) {
c.onMessageEvent(botClient, event)
})
syncer.OnEventType(mevt.Type{Type: "m.room.bot.options", Class: mevt.UnknownEventType}, func(event *mevt.Event) {
c.onBotOptionsEvent(client, event)
syncer.OnEventType(mevt.Type{Type: "m.room.bot.options", Class: mevt.UnknownEventType}, func(_ mautrix.EventSource, event *mevt.Event) {
c.onBotOptionsEvent(botClient.Client, event)
})
if config.AutoJoinRooms {
syncer.OnEventType(mevt.StateMember, func(event *mevt.Event) {
syncer.OnEventType(mevt.StateMember, func(_ mautrix.EventSource, event *mevt.Event) {
c.onRoomMemberEvent(client, event)
})
}
// When receiving an encrypted event, attempt to decrypt it using the BotClient's capabilities.
// If successfully decrypted propagate the decrypted event to the clients.
syncer.OnEventType(mevt.EventEncrypted, func(source mautrix.EventSource, evt *mevt.Event) {
if err := evt.Content.ParseRaw(mevt.EventEncrypted); err != nil {
log.WithError(err).Error("Failed to parse encrypted message")
return
}
encContent := evt.Content.AsEncrypted()
decrypted, err := botClient.DecryptMegolmEvent(evt)
if err != nil {
log.WithFields(log.Fields{
"user_id": config.UserID,
"device_id": encContent.DeviceID,
"session_id": encContent.SessionID,
"sender_key": encContent.SenderKey,
}).WithError(err).Error("Failed to decrypt message")
} else {
if decrypted.Type == mevt.EventMessage {
err = decrypted.Content.ParseRaw(mevt.EventMessage)
if err != nil {
log.WithError(err).Error("Could not parse decrypted message event")
} else {
c.onMessageEvent(botClient, decrypted)
}
}
log.WithFields(log.Fields{
"type": evt.Type,
"sender": evt.Sender,
"room_id": evt.RoomID,
"state_key": evt.StateKey,
}).Trace("Decrypted event successfully")
}
})
// Ignore events before neb's join event.
eventIgnorer := mautrix.OldEventIgnorer{UserID: config.UserID}
eventIgnorer.Register(syncer)
log.WithFields(log.Fields{
"user_id": config.UserID,
"device_id": config.DeviceID,
"sync": config.Sync,
"auto_join_rooms": config.AutoJoinRooms,
"since": nebStore.LoadNextBatch(config.UserID),
}).Info("Created new client")
if config.Sync {
go func() {
for {
if e := client.Sync(); e != nil {
log.WithFields(log.Fields{
log.ErrorKey: e,
"user_id": config.UserID,
}).Error("Fatal Sync() error")
time.Sleep(10 * time.Second)
} else {
log.WithField("user_id", config.UserID).Info("Stopping Sync()")
return
}
}
}()
go botClient.Sync()
}
return client, nil
return nil
}

5
clients/clients_test.go

@ -30,7 +30,7 @@ type MockService struct {
commands []types.Command
}
func (s *MockService) Commands(cli *mautrix.Client) []types.Command {
func (s *MockService) Commands(cli types.MatrixClient) []types.Command {
return s.commands
}
@ -76,6 +76,7 @@ func TestCommandParsing(t *testing.T) {
clients := New(&store, cli)
mxCli, _ := mautrix.NewClient("https://someplace.somewhere", "@service:user", "token")
mxCli.Client = cli
botClient := BotClient{Client: mxCli}
for _, input := range commandParseTests {
executedCmdArgs = []string{}
@ -94,7 +95,7 @@ func TestCommandParsing(t *testing.T) {
RoomID: "!foo:bar",
Content: content,
}
clients.onMessageEvent(mxCli, &event)
clients.onMessageEvent(&botClient, &event)
if !reflect.DeepEqual(executedCmdArgs, input.expectArgs) {
t.Errorf("TestCommandParsing want %s, got %s", input.expectArgs, executedCmdArgs)
}

28
clients/crypto_logger.go

@ -0,0 +1,28 @@
package clients
import (
log "github.com/sirupsen/logrus"
)
// CryptoMachineLogger wraps around the usual logger, implementing the Logger interface needed by OlmMachine.
type CryptoMachineLogger struct{}
// Error formats and logs an error message.
func (CryptoMachineLogger) Error(message string, args ...interface{}) {
log.Errorf(message, args...)
}
// Warn formats and logs a warning message.
func (CryptoMachineLogger) Warn(message string, args ...interface{}) {
log.Warnf(message, args...)
}
// Debug formats and logs a debug message.
func (CryptoMachineLogger) Debug(message string, args ...interface{}) {
log.Debugf(message, args...)
}
// Trace formats and logs a trace message.
func (CryptoMachineLogger) Trace(message string, args ...interface{}) {
log.Tracef(message, args...)
}

79
clients/state_store.go

@ -0,0 +1,79 @@
package clients
import (
"errors"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// NebStateStore implements the StateStore interface for OlmMachine.
// It is used to determine which rooms are encrypted and which rooms are shared with a user.
// The state is updated by /sync responses.
type NebStateStore struct {
Storer *mautrix.InMemoryStore
}
// IsEncrypted returns whether a room has been encrypted.
func (ss *NebStateStore) IsEncrypted(roomID id.RoomID) bool {
room := ss.Storer.LoadRoom(roomID)
if room == nil {
return false
}
_, ok := room.State[event.StateEncryption]
return ok
}
// FindSharedRooms returns a list of room IDs that the given user ID is also a member of.
func (ss *NebStateStore) FindSharedRooms(userID id.UserID) []id.RoomID {
sharedRooms := make([]id.RoomID, 0)
for roomID, room := range ss.Storer.Rooms {
if room.GetMembershipState(userID) != event.MembershipLeave {
sharedRooms = append(sharedRooms, roomID)
}
}
return sharedRooms
}
// UpdateStateStore updates the internal state of NebStateStore from a /sync response.
func (ss *NebStateStore) UpdateStateStore(resp *mautrix.RespSync) {
for roomID, evts := range resp.Rooms.Join {
room := ss.Storer.LoadRoom(roomID)
if room == nil {
room = mautrix.NewRoom(roomID)
ss.Storer.SaveRoom(room)
}
for _, i := range evts.State.Events {
room.UpdateState(i)
}
for _, i := range evts.Timeline.Events {
if i.Type.IsState() {
room.UpdateState(i)
}
}
}
}
// GetJoinedMembers returns a list of members that are currently in a room.
func (ss *NebStateStore) GetJoinedMembers(roomID id.RoomID) ([]id.UserID, error) {
joinedMembers := make([]id.UserID, 0)
room := ss.Storer.LoadRoom(roomID)
if room == nil {
return nil, errors.New("unknown roomID")
}
memberEvents := room.State[event.StateMember]
if memberEvents == nil {
return nil, errors.New("no state member events found")
}
for stateKey, stateEvent := range memberEvents {
if stateEvent == nil {
continue
}
stateEvent.Content.ParseRaw(event.StateMember)
if stateEvent.Content.AsMember().Membership == event.MembershipJoin {
joinedMembers = append(joinedMembers, id.UserID(stateKey))
}
}
return joinedMembers, nil
}

8
database/db.go

@ -14,6 +14,7 @@ import (
// A ServiceDB stores the configuration for the services
type ServiceDB struct {
db *sql.DB
dialect string
}
// A single global instance of the service DB.
@ -44,7 +45,7 @@ func Open(databaseType, databaseURL string) (serviceDB *ServiceDB, err error) {
// https://github.com/mattn/go-sqlite3/issues/274
db.SetMaxOpenConns(1)
}
serviceDB = &ServiceDB{db: db}
serviceDB = &ServiceDB{db: db, dialect: databaseType}
return
}
@ -327,6 +328,11 @@ func (d *ServiceDB) InsertFromConfig(cfg *api.ConfigFile) error {
return nil
}
// GetSQLDb retrieves the SQL database instance of a ServiceDB and the dialect it uses (sqlite3 or postgres).
func (d *ServiceDB) GetSQLDb() (*sql.DB, string) {
return d.db, d.dialect
}
func runTransaction(db *sql.DB, fn func(txn *sql.Tx) error) (err error) {
txn, err := db.Begin()
if err != nil {

9
go.mod

@ -23,7 +23,7 @@ require (
github.com/json-iterator/go v1.1.9 // indirect
github.com/julienschmidt/httprouter v1.2.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.3.0
github.com/lib/pq v1.7.0
github.com/matrix-org/dugong v0.0.0-20180820122854-51a565b5666b
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7
github.com/mattn/go-shellwords v1.0.10
@ -45,12 +45,11 @@ require (
github.com/sirupsen/logrus v1.4.2
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
golang.org/dl v0.0.0-20200601221412-a954fa24b3e5 // indirect
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/tools v0.0.0-20200311090712-aafaee8bce8c // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.8
maunium.net/go/gomuks v0.1.0
maunium.net/go/mautrix v0.4.7
gopkg.in/yaml.v2 v2.3.0
maunium.net/go/mautrix v0.5.5
)

17
go.sum

@ -79,6 +79,8 @@ 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/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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=
@ -94,6 +96,7 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
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-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
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=
@ -173,6 +176,7 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
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/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
@ -207,8 +211,11 @@ 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-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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -224,6 +231,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
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=
@ -251,11 +259,16 @@ 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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
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=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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/mautrix v0.5.0-rc.1 h1:Ux2rwQfgf044lXjzCXmaY6VVJPcLioNzdN3okbOVIlg=
maunium.net/go/mautrix v0.5.0-rc.1/go.mod h1:LnkFnB1yjCbb8V+upoEHDGvI/F38NHSTWYCe2RRJgSY=
maunium.net/go/mautrix v0.5.5 h1:e0Pql1FdxoNUudx2oXo1gZHMrqIh5MC72cdXEPIrYLA=
maunium.net/go/mautrix v0.5.5/go.mod h1:FLbMANzwqlsX2Fgm7SDe+E4I3wSa4UxJRKqS5wGkCwA=
maunium.net/go/mauview v0.1.1/go.mod h1:3QBUiuLct9moP1LgDhCGIg0Ovxn38Bd2sGndnUOuj4o=
maunium.net/go/tcell v0.2.0/go.mod h1:9Apcb3lNNS6C6lCqKT9UFp7BTRzHXfWE+/tgufsAMho=

2
goneb.go

@ -139,7 +139,7 @@ func insertServicesFromConfig(clis *clients.Clients, serviceReqs []api.Configure
}
func loadDatabase(databaseType, databaseURL, configYAML string) (*database.ServiceDB, error) {
if configYAML != "" {
if databaseType == "" && databaseURL == "" {
databaseType = "sqlite3"
databaseURL = ":memory:?_busy_timeout=5000"
}

8
services/alertmanager/alertmanager.go

@ -13,7 +13,6 @@ import (
"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"
)
@ -78,7 +77,7 @@ type WebhookNotification struct {
}
// OnReceiveWebhook receives requests from Alertmanager and sends requests to Matrix as a result.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli types.MatrixClient) {
decoder := json.NewDecoder(req.Body)
var notif WebhookNotification
if err := decoder.Decode(&notif); err != nil {
@ -144,7 +143,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
}
// Register makes sure the Config information supplied is valid.
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
s.WebhookURL = s.webhookEndpointURL
for _, templates := range s.Rooms {
// validate that we have at least a plain text template
@ -191,13 +190,12 @@ func (s *Service) PostRegister(oldService types.Service) {
}
}
func (s *Service) joinRooms(client *mautrix.Client) {
func (s *Service) joinRooms(client types.MatrixClient) {
for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": roomID,
"user_id": client.UserID,
}).Error("Failed to join room")
}
}

2
services/alertmanager/alertmanager_test.go

@ -93,7 +93,7 @@ func TestNotify(t *testing.T) {
}
}
func buildTestClient(msgs *[]mevt.MessageEventContent) *mautrix.Client {
func buildTestClient(msgs *[]mevt.MessageEventContent) types.MatrixClient {
matrixTrans := struct{ testutils.MockTransport }{}
matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
if !strings.Contains(req.URL.String(), "/send/m.room.message") {

5
services/echo/echo.go

@ -5,7 +5,6 @@ import (
"strings"
"github.com/matrix-org/go-neb/types"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -21,9 +20,9 @@ type Service struct {
// Commands supported:
// !echo some message
// Responds with a notice of "some message".
func (e *Service) Commands(cli *mautrix.Client) []types.Command {
func (e *Service) Commands(cli types.MatrixClient) []types.Command {
return []types.Command{
types.Command{
{
Path: []string{"echo"},
Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
return &mevt.MessageEventContent{

5
services/giphy/giphy.go

@ -11,7 +11,6 @@ import (
"github.com/matrix-org/go-neb/types"
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"
@ -61,7 +60,7 @@ type Service struct {
// Commands supported:
// !giphy some search query without quotes
// Responds with a suitable GIF into the same room as the command.
func (s *Service) Commands(client *mautrix.Client) []types.Command {
func (s *Service) Commands(client types.MatrixClient) []types.Command {
return []types.Command{
types.Command{
Path: []string{"giphy"},
@ -72,7 +71,7 @@ func (s *Service) Commands(client *mautrix.Client) []types.Command {
}
}
func (s *Service) cmdGiphy(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
func (s *Service) cmdGiphy(client types.MatrixClient, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// only 1 arg which is the text to search for.
query := strings.Join(args, " ")
gifResult, err := s.searchGiphy(query)

7
services/github/github.go

@ -22,7 +22,6 @@ import (
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -563,7 +562,7 @@ func (s *Service) expandCommit(roomID id.RoomID, userID id.UserID, owner, repo,
// Responds with the outcome of the issue comment creation request. This command requires
// a Github account to be linked to the Matrix user ID issuing the command. If there
// is no link, it will return a Starter Link instead.
func (s *Service) Commands(cli *mautrix.Client) []types.Command {
func (s *Service) Commands(cli types.MatrixClient) []types.Command {
return []types.Command{
{
Path: []string{"github", "search"},
@ -632,7 +631,7 @@ func (s *Service) Commands(cli *mautrix.Client) []types.Command {
// it will also expand strings of the form:
// #12
// using the default repository.
func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
func (s *Service) Expansions(cli types.MatrixClient) []types.Expansion {
return []types.Expansion{
types.Expansion{
Regexp: ownerRepoIssueRegex,
@ -718,7 +717,7 @@ func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
}
// Register makes sure that the given realm ID maps to a github realm.
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
if s.RealmID == "" {
return fmt.Errorf("RealmID is required")
}

7
services/github/github_webhook.go

@ -13,7 +13,6 @@ import (
"github.com/matrix-org/go-neb/services/github/webhook"
"github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -82,7 +81,7 @@ type WebhookService struct {
//
// If the "owner/repo" string doesn't exist in this Service config, then the webhook will be deleted from
// Github.
func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli types.MatrixClient) {
evType, repo, msg, err := webhook.OnReceiveRequest(req, s.SecretToken)
if err != nil {
w.WriteHeader(err.Code)
@ -146,7 +145,7 @@ func (s *WebhookService) OnReceiveWebhook(w http.ResponseWriter, req *http.Reque
//
// Hooks can get out of sync if a user manually deletes a hook in the Github UI. In this case, toggling the repo configuration will
// force NEB to recreate the hook.
func (s *WebhookService) Register(oldService types.Service, client *mautrix.Client) error {
func (s *WebhookService) Register(oldService types.Service, client types.MatrixClient) error {
if s.RealmID == "" || s.ClientUserID == "" {
return fmt.Errorf("RealmID and ClientUserID is required")
}
@ -252,7 +251,7 @@ func (s *WebhookService) PostRegister(oldService types.Service) {
}
}
func (s *WebhookService) joinWebhookRooms(client *mautrix.Client) error {
func (s *WebhookService) joinWebhookRooms(client types.MatrixClient) error {
for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
// TODO: Leave the rooms we successfully joined?

6
services/google/google.go

@ -13,7 +13,6 @@ import (
"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"
)
@ -70,7 +69,7 @@ type Service struct {
// Commands supported:
// !google image some_search_query_without_quotes
// Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *mautrix.Client) []types.Command {
func (s *Service) Commands(client types.MatrixClient) []types.Command {
return []types.Command{
{
Path: []string{"google", "image"},
@ -101,7 +100,8 @@ func usageMessage() *mevt.MessageEventContent {
}
}
func (s *Service) cmdGoogleImgSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
func (s *Service) cmdGoogleImgSearch(client types.MatrixClient, roomID id.RoomID, userID id.UserID,
args []string) (interface{}, error) {
if len(args) < 1 {
return usageMessage(), nil

5
services/guggy/guggy.go

@ -12,7 +12,6 @@ import (
"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"
)
@ -51,7 +50,7 @@ type Service struct {
// Commands supported:
// !guggy some search query without quotes
// Responds with a suitable GIF into the same room as the command.
func (s *Service) Commands(client *mautrix.Client) []types.Command {
func (s *Service) Commands(client types.MatrixClient) []types.Command {
return []types.Command{
{
Path: []string{"guggy"},
@ -61,7 +60,7 @@ func (s *Service) Commands(client *mautrix.Client) []types.Command {
},
}
}
func (s *Service) cmdGuggy(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
func (s *Service) cmdGuggy(client types.MatrixClient, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// only 1 arg which is the text to search for.
querySentence := strings.Join(args, " ")
gifResult, err := s.text2gifGuggy(querySentence)

5
services/imgur/imgur.go

@ -12,7 +12,6 @@ import (
"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"
)
@ -116,7 +115,7 @@ type Service struct {
// Commands supported:
// !imgur some_search_query_without_quotes
// Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *mautrix.Client) []types.Command {
func (s *Service) Commands(client types.MatrixClient) []types.Command {
return []types.Command{
{
Path: []string{"imgur", "help"},
@ -142,7 +141,7 @@ func usageMessage() *mevt.MessageEventContent {
}
// Search Imgur for a relevant image and upload it to matrix
func (s *Service) cmdImgSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
func (s *Service) cmdImgSearch(client types.MatrixClient, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// Check for query text
if len(args) < 1 {
return usageMessage(), nil

11
services/jira/jira.go

@ -21,7 +21,6 @@ import (
"github.com/matrix-org/go-neb/services/utils"
"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"
)
@ -76,7 +75,7 @@ type Service struct {
// Register ensures that the given realm IDs are valid JIRA realms and registers webhooks
// with those JIRA endpoints.
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
// 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
// need to do this for each unique realm.
@ -242,7 +241,7 @@ func (s *Service) expandIssue(roomID id.RoomID, userID id.UserID, issueKeyGroups
// 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
// if there is a known public project with that project key.
func (s *Service) Commands(cli *mautrix.Client) []types.Command {
func (s *Service) Commands(cli types.MatrixClient) []types.Command {
return []types.Command{
types.Command{
Path: []string{"jira", "create"},
@ -259,9 +258,9 @@ func (s *Service) Commands(cli *mautrix.Client) []types.Command {
// 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
// be chosen arbitrarily.
func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
func (s *Service) Expansions(cli types.MatrixClient) []types.Expansion {
return []types.Expansion{
types.Expansion{
{
Regexp: issueKeyRegex,
Expand: func(roomID id.RoomID, userID id.UserID, issueKeyGroups []string) interface{} {
return s.expandIssue(roomID, userID, issueKeyGroups)
@ -271,7 +270,7 @@ func (s *Service) Expansions(cli *mautrix.Client) []types.Expansion {
}
// 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 *mautrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli types.MatrixClient) {
eventProjectKey, event, httpErr := webhook.OnReceiveRequest(req)
if httpErr != nil {
log.Print("Failed to handle JIRA webhook")

10
services/rssbot/rssbot.go

@ -19,7 +19,6 @@ import (
"github.com/mmcdole/gofeed"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
mevt "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -102,7 +101,7 @@ type Service struct {
}
// 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 *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
if len(s.Feeds) == 0 {
// this is an error UNLESS the old service had some feeds in which case they are deleting us :(
var numOldFeeds int
@ -131,7 +130,7 @@ func (s *Service) Register(oldService types.Service, client *mautrix.Client) err
return nil
}
func (s *Service) joinRooms(client *mautrix.Client) {
func (s *Service) joinRooms(client types.MatrixClient) {
roomSet := make(map[id.RoomID]bool)
for _, feedInfo := range s.Feeds {
for _, roomID := range feedInfo.Rooms {
@ -144,7 +143,6 @@ func (s *Service) joinRooms(client *mautrix.Client) {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": roomID,
"user_id": client.UserID,
}).Error("Failed to join room")
}
}
@ -175,7 +173,7 @@ func (s *Service) PostRegister(oldService types.Service) {
// - Else if there is a Title field, use it as the GUID.
//
// Returns a timestamp representing when this Service should have OnPoll called again.
func (s *Service) OnPoll(cli *mautrix.Client) time.Time {
func (s *Service) OnPoll(cli types.MatrixClient) time.Time {
logger := log.WithFields(log.Fields{
"service_id": s.ServiceID(),
"service_type": s.ServiceType(),
@ -408,7 +406,7 @@ func (s *Service) newItems(feedURL string, allItems []*gofeed.Item) (items []gof
return
}
func (s *Service) sendToRooms(cli *mautrix.Client, feedURL string, feed *gofeed.Feed, item gofeed.Item) error {
func (s *Service) sendToRooms(cli types.MatrixClient, feedURL string, feed *gofeed.Feed, item gofeed.Item) error {
logger := log.WithFields(log.Fields{
"feed_url": feedURL,
"title": item.Title,

6
services/slackapi/slackapi.go

@ -6,7 +6,6 @@ import (
"github.com/matrix-org/go-neb/types"
log "github.com/sirupsen/logrus"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -37,7 +36,7 @@ type Service struct {
// to Matrix as a result.
//
// 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 *mautrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli types.MatrixClient) {
segments := strings.Split(req.URL.Path, "/")
if len(segments) < 2 {
@ -72,13 +71,12 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
}
// Register joins the configured room and sets the public WebhookURL
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
s.WebhookURL = s.webhookEndpointURL
if _, err := client.JoinRoom(s.RoomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": s.RoomID,
"user_id": client.UserID,
}).Error("Failed to join room")
}
return nil

8
services/travisci/travisci.go

@ -13,7 +13,6 @@ import (
"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"
)
@ -180,7 +179,7 @@ func outputForTemplate(travisTmpl string, tmpl map[string]string) (out string) {
// webhooks: http://go-neb-endpoint.com/notifications
//
// See https://docs.travis-ci.com/user/notifications#Webhook-notifications for more information.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli types.MatrixClient) {
if err := req.ParseForm(); err != nil {
log.WithError(err).Error("Failed to read incoming Travis-CI webhook form")
w.WriteHeader(400)
@ -243,7 +242,7 @@ func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli
}
// Register makes sure the Config information supplied is valid.
func (s *Service) Register(oldService types.Service, client *mautrix.Client) error {
func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
s.WebhookURL = s.webhookEndpointURL
for _, roomData := range s.Rooms {
for repo := range roomData.Repos {
@ -275,13 +274,12 @@ func (s *Service) PostRegister(oldService types.Service) {
}
}
func (s *Service) joinRooms(client *mautrix.Client) {
func (s *Service) joinRooms(client types.MatrixClient) {
for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": roomID,
"user_id": client.UserID,
}).Error("Failed to join room")
}
}

5
services/wikipedia/wikipedia.go

@ -12,7 +12,6 @@ import (
"github.com/jaytaylor/html2text"
"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"
)
@ -51,7 +50,7 @@ type Service struct {
// Commands supported:
// !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.
func (s *Service) Commands(client *mautrix.Client) []types.Command {
func (s *Service) Commands(client types.MatrixClient) []types.Command {
return []types.Command{
{
Path: []string{"wikipedia"},
@ -70,7 +69,7 @@ func usageMessage() *mevt.MessageEventContent {
}
}
func (s *Service) cmdWikipediaSearch(client *mautrix.Client, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
func (s *Service) cmdWikipediaSearch(client types.MatrixClient, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
// Check for query text
if len(args) < 1 {
return usageMessage(), nil

30
types/service.go

@ -9,6 +9,7 @@ import (
"time"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -24,7 +25,18 @@ type BotOptions struct {
type Poller interface {
// 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.
OnPoll(client *mautrix.Client) time.Time
OnPoll(client MatrixClient) time.Time
}
// MatrixClient represents an object that can communicate with a Matrix server in certain ways that services require.
type MatrixClient interface {
// Join a room by ID or alias. Content can optionally specify the request body.
JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *mautrix.RespJoinRoom, err error)
// Send a message event to a room.
SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{},
extra ...mautrix.ReqSendEvent) (resp *mautrix.RespSendEvent, err error)
// Upload an HTTP URL.
UploadLink(link string) (*mautrix.RespMediaUpload, error)
}
// A Service is the configuration for a bot service.
@ -35,14 +47,14 @@ type Service interface {
ServiceID() string
// Return the type of service. This string MUST NOT change.
ServiceType() string
Commands(cli *mautrix.Client) []Command
Expansions(cli *mautrix.Client) []Expansion
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client)
Commands(cli MatrixClient) []Command
Expansions(cli MatrixClient) []Expansion
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli MatrixClient)
// 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
// 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.
Register(oldService Service, client *mautrix.Client) error
Register(oldService Service, client MatrixClient) error
// 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
// concurrent modifications to this service whilst this function executes. This lifecycle hook should be used to clean
@ -83,23 +95,23 @@ func (s *DefaultService) ServiceType() string {
}
// Commands returns no commands.
func (s *DefaultService) Commands(cli *mautrix.Client) []Command {
func (s *DefaultService) Commands(cli MatrixClient) []Command {
return []Command{}
}
// Expansions returns no expansions.
func (s *DefaultService) Expansions(cli *mautrix.Client) []Expansion {
func (s *DefaultService) Expansions(cli MatrixClient) []Expansion {
return []Expansion{}
}
// Register does nothing and returns no error.
func (s *DefaultService) Register(oldService Service, client *mautrix.Client) error { return nil }
func (s *DefaultService) Register(oldService Service, cli MatrixClient) error { return nil }
// PostRegister does nothing.
func (s *DefaultService) PostRegister(oldService Service) {}
// OnReceiveWebhook does nothing but 200 OK the request.
func (s *DefaultService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *mautrix.Client) {
func (s *DefaultService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli MatrixClient) {
w.WriteHeader(200) // Do nothing
}

Loading…
Cancel
Save