Browse Source

Remove Plugin. Replace with Commands() and Expansions()

Also split out `types.go` into separate files by the area they cover
(services, auth, actions).
kegan/remove-plugin
Kegan Dougal 8 years ago
parent
commit
3e21e02171
  1. 118
      src/github.com/matrix-org/go-neb/clients/clients.go
  2. 179
      src/github.com/matrix-org/go-neb/plugin/plugin.go
  3. 159
      src/github.com/matrix-org/go-neb/plugin/plugin_test.go
  4. 18
      src/github.com/matrix-org/go-neb/services/echo/echo.go
  5. 15
      src/github.com/matrix-org/go-neb/services/giphy/giphy.go
  6. 103
      src/github.com/matrix-org/go-neb/services/github/github.go
  7. 22
      src/github.com/matrix-org/go-neb/services/guggy/guggy.go
  8. 41
      src/github.com/matrix-org/go-neb/services/jira/jira.go
  9. 36
      src/github.com/matrix-org/go-neb/types/actions.go
  10. 56
      src/github.com/matrix-org/go-neb/types/auth.go
  11. 66
      src/github.com/matrix-org/go-neb/types/service.go

118
src/github.com/matrix-org/go-neb/clients/clients.go

@ -1,16 +1,18 @@
package clients
import (
"net/http"
"net/url"
"strings"
"sync"
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/api"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/metrics"
"github.com/matrix-org/go-neb/types"
"net/http"
"net/url"
"strings"
"sync"
shellwords "github.com/mattn/go-shellwords"
)
type nextBatchStore struct {
@ -179,11 +181,111 @@ func (c *Clients) onMessageEvent(client *matrix.Client, event *matrix.Event) {
"service_user_id": client.UserID,
}).Warn("Error loading services")
}
var plugins []plugin.Plugin
body, ok := event.Body()
if !ok || body == "" {
return
}
// filter m.notice to prevent loops
if msgtype, ok := event.MessageType(); !ok || msgtype == "m.notice" {
return
}
var responses []interface{}
for _, service := range services {
plugins = append(plugins, service.Plugin(client, event.RoomID))
if body[0] == '!' { // message is a command
args, err := shellwords.Parse(body[1:])
if err != nil {
args = strings.Split(body[1:], " ")
}
if response := runCommandForService(service.Commands(client, event.RoomID), event, args); response != nil {
responses = append(responses, response)
}
} else { // message isn't a command, it might need expanding
expansions := runExpansionsForService(service.Expansions(client, event.RoomID), event, body)
responses = append(responses, expansions...)
}
}
for _, content := range responses {
if _, err := client.SendMessageEvent(event.RoomID, "m.room.message", 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")
}
}
}
// runCommandForService runs a single command read from a matrix event. Runs
// 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
// response is appropriate.
func runCommandForService(cmds []types.Command, event *matrix.Event, arguments []string) interface{} {
var bestMatch *types.Command
for _, command := range cmds {
matches := command.Matches(arguments)
betterMatch := bestMatch == nil || len(bestMatch.Path) < len(command.Path)
if matches && betterMatch {
bestMatch = &command
}
}
if bestMatch == nil {
return nil
}
cmdArgs := arguments[len(bestMatch.Path):]
log.WithFields(log.Fields{
"room_id": event.RoomID,
"user_id": event.Sender,
"command": bestMatch.Path,
}).Info("Executing command")
content, err := bestMatch.Command(event.RoomID, event.Sender, cmdArgs)
if err != nil {
if content != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": event.RoomID,
"user_id": event.Sender,
"command": bestMatch.Path,
"args": cmdArgs,
}).Warn("Command returned both error and content.")
}
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusFailure)
content = matrix.TextMessage{"m.notice", err.Error()}
} else {
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusSuccess)
}
plugin.OnMessage(plugins, client, event)
return content
}
// run the expansions for a matrix event.
func runExpansionsForService(expans []types.Expansion, event *matrix.Event, body string) []interface{} {
var responses []interface{}
for _, expansion := range expans {
matches := map[string]bool{}
for _, matchingGroups := range expansion.Regexp.FindAllStringSubmatch(body, -1) {
matchingText := matchingGroups[0] // first element is always the complete match
if matches[matchingText] {
// Only expand the first occurance of a matching string
continue
}
matches[matchingText] = true
if response := expansion.Expand(event.RoomID, event.Sender, matchingGroups); response != nil {
responses = append(responses, response)
}
}
}
return responses
}
func (c *Clients) onBotOptionsEvent(client *matrix.Client, event *matrix.Event) {

179
src/github.com/matrix-org/go-neb/plugin/plugin.go

@ -1,179 +0,0 @@
package plugin
import (
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/metrics"
"github.com/mattn/go-shellwords"
"regexp"
"strings"
)
// A Plugin is a list of commands and expansions to apply to incoming messages.
type Plugin struct {
Commands []Command
Expansions []Expansion
}
// A Command is something that a user invokes by sending a message starting with '!'
// followed by a list of strings that name the command, followed by a list of argument
// strings. The argument strings may be quoted using '\"' and '\'' in the same way
// that they are quoted in the unix shell.
type Command struct {
Path []string
Arguments []string
Help string
Command func(roomID, userID string, arguments []string) (content interface{}, err error)
}
// An Expansion is something that actives when the user sends any message
// containing a string matching a given pattern. For example an RFC expansion
// might expand "RFC 6214" into "Adaptation of RFC 1149 for IPv6" and link to
// the appropriate RFC.
type Expansion struct {
Regexp *regexp.Regexp
Expand func(roomID, userID string, matchingGroups []string) interface{}
}
// matches if the arguments start with the path of the command.
func (command *Command) matches(arguments []string) bool {
if len(arguments) < len(command.Path) {
return false
}
for i, segment := range command.Path {
if segment != arguments[i] {
return false
}
}
return true
}
// runCommandForPlugin runs a single command read from a matrix event. Runs
// 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
// response is appropriate.
func runCommandForPlugin(plugin Plugin, event *matrix.Event, arguments []string) interface{} {
var bestMatch *Command
for _, command := range plugin.Commands {
matches := command.matches(arguments)
betterMatch := bestMatch == nil || len(bestMatch.Path) < len(command.Path)
if matches && betterMatch {
bestMatch = &command
}
}
if bestMatch == nil {
return nil
}
cmdArgs := arguments[len(bestMatch.Path):]
log.WithFields(log.Fields{
"room_id": event.RoomID,
"user_id": event.Sender,
"command": bestMatch.Path,
}).Info("Executing command")
content, err := bestMatch.Command(event.RoomID, event.Sender, cmdArgs)
if err != nil {
if content != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": event.RoomID,
"user_id": event.Sender,
"command": bestMatch.Path,
"args": cmdArgs,
}).Warn("Command returned both error and content.")
}
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusFailure)
content = matrix.TextMessage{"m.notice", err.Error()}
} else {
metrics.IncrementCommand(bestMatch.Path[0], metrics.StatusSuccess)
}
return content
}
// run the expansions for a matrix event.
func runExpansionsForPlugin(plugin Plugin, event *matrix.Event, body string) []interface{} {
var responses []interface{}
for _, expansion := range plugin.Expansions {
matches := map[string]bool{}
for _, matchingGroups := range expansion.Regexp.FindAllStringSubmatch(body, -1) {
matchingText := matchingGroups[0] // first element is always the complete match
if matches[matchingText] {
// Only expand the first occurance of a matching string
continue
}
matches[matchingText] = true
if response := expansion.Expand(event.RoomID, event.Sender, matchingGroups); response != nil {
responses = append(responses, response)
}
}
}
return responses
}
// runCommands runs the plugin commands or expansions for a single matrix
// event. Returns a list of JSON encodable contents for the matrix messages
// to use as responses.
// If the message beings with '!' then it is assumed to be a command. Each
// plugin is checked for a matching command, if a match is found then that
// command is run. If more than one plugin has a matching command then all
// of those commands are run. This shouldn't happen unless the same plugin
// is installed multiple times since each plugin will usually have a
// distinct prefix for its commands.
// If the message doesn't begin with '!' then it is checked against the
// expansions for each plugin.
func runCommands(plugins []Plugin, event *matrix.Event) []interface{} {
body, ok := event.Body()
if !ok || body == "" {
return nil
}
// filter m.notice to prevent loops
if msgtype, ok := event.MessageType(); !ok || msgtype == "m.notice" {
return nil
}
var responses []interface{}
if body[0] == '!' {
args, err := shellwords.Parse(body[1:])
if err != nil {
args = strings.Split(body[1:], " ")
}
for _, plugin := range plugins {
if response := runCommandForPlugin(plugin, event, args); response != nil {
responses = append(responses, response)
}
}
} else {
for _, plugin := range plugins {
expansions := runExpansionsForPlugin(plugin, event, body)
responses = append(responses, expansions...)
}
}
return responses
}
// OnMessage checks the message event to see whether it contains any commands
// or expansions from the listed plugins and processes those commands or
// expansions.
func OnMessage(plugins []Plugin, client *matrix.Client, event *matrix.Event) {
responses := runCommands(plugins, event)
for _, content := range responses {
_, err := client.SendMessageEvent(event.RoomID, "m.room.message", content)
if 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")
}
}
}

159
src/github.com/matrix-org/go-neb/plugin/plugin_test.go

@ -1,159 +0,0 @@
package plugin
import (
"github.com/matrix-org/go-neb/matrix"
"reflect"
"regexp"
"testing"
)
const (
myRoomID = "!room:example.com"
mySender = "@user:example.com"
)
func makeTestEvent(msgtype, body string) *matrix.Event {
return &matrix.Event{
Sender: mySender,
Type: "m.room.message",
RoomID: myRoomID,
Content: map[string]interface{}{
"body": body,
"msgtype": msgtype,
},
}
}
type testResponse struct {
RoomID string
Arguments []string
}
func makeTestResponse(roomID, sender string, arguments []string) interface{} {
return testResponse{roomID, arguments}
}
type testExpansion struct {
RoomID string
UserID string
MatchingGroups []string
}
func makeTestExpansion(roomID, userID string, matchingGroups []string) interface{} {
return testExpansion{roomID, userID, matchingGroups}
}
func makeTestPlugin(paths [][]string, regexps []*regexp.Regexp) Plugin {
var commands []Command
for _, path := range paths {
commands = append(commands, Command{
Path: path,
Command: func(roomID, sender string, arguments []string) (interface{}, error) {
return makeTestResponse(roomID, sender, arguments), nil
},
})
}
var expansions []Expansion
for _, re := range regexps {
expansions = append(expansions, Expansion{
Regexp: re,
Expand: makeTestExpansion,
})
}
return Plugin{Commands: commands, Expansions: expansions}
}
func TestRunCommands(t *testing.T) {
plugins := []Plugin{makeTestPlugin([][]string{
[]string{"test", "command"},
}, nil)}
event := makeTestEvent("m.text", `!test command arg1 "arg 2" 'arg 3'`)
got := runCommands(plugins, event)
want := []interface{}{makeTestResponse(myRoomID, mySender, []string{
"arg1", "arg 2", "arg 3",
})}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}
func TestRunCommandsBestMatch(t *testing.T) {
plugins := []Plugin{makeTestPlugin([][]string{
[]string{"test", "command"},
[]string{"test", "command", "more", "specific"},
}, nil)}
event := makeTestEvent("m.text", "!test command more specific arg1")
got := runCommands(plugins, event)
want := []interface{}{makeTestResponse(myRoomID, mySender, []string{
"arg1",
})}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}
func TestRunCommandsMultiplePlugins(t *testing.T) {
plugins := []Plugin{
makeTestPlugin([][]string{[]string{"test", "command", "first"}}, nil),
makeTestPlugin([][]string{[]string{"test", "command"}}, nil),
}
event := makeTestEvent("m.text", "!test command first arg1")
got := runCommands(plugins, event)
want := []interface{}{
makeTestResponse(myRoomID, mySender, []string{"arg1"}),
makeTestResponse(myRoomID, mySender, []string{"first", "arg1"}),
}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}
func TestRunCommandsInvalidShell(t *testing.T) {
plugins := []Plugin{
makeTestPlugin([][]string{[]string{"test", "command"}}, nil),
}
event := makeTestEvent("m.text", `!test command 'mismatched quotes"`)
got := runCommands(plugins, event)
want := []interface{}{
makeTestResponse(myRoomID, mySender, []string{"'mismatched", `quotes"`}),
}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}
func TestExpansion(t *testing.T) {
plugins := []Plugin{
makeTestPlugin(nil, []*regexp.Regexp{
regexp.MustCompile("a[^ ]*"),
regexp.MustCompile("b.."),
}),
}
event := makeTestEvent("m.text", "test banana for scale")
got := runCommands(plugins, event)
want := []interface{}{
makeTestExpansion(myRoomID, mySender, []string{"anana"}),
makeTestExpansion(myRoomID, mySender, []string{"ale"}),
makeTestExpansion(myRoomID, mySender, []string{"ban"}),
}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}
func TestExpansionDuplicateMatches(t *testing.T) {
plugins := []Plugin{
makeTestPlugin(nil, []*regexp.Regexp{
regexp.MustCompile("badger"),
}),
}
event := makeTestEvent("m.text", "badger badger badger")
got := runCommands(plugins, event)
want := []interface{}{
makeTestExpansion(myRoomID, mySender, []string{"badger"}),
}
if !reflect.DeepEqual(got, want) {
t.Errorf("runCommands(\nplugins=%+v\nevent=%+v\n)\n%+v\nwanted: %+v", plugins, event, got, want)
}
}

18
src/github.com/matrix-org/go-neb/services/echo/echo.go

@ -1,10 +1,10 @@
package services
import (
"strings"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/types"
"strings"
)
type echoService struct {
@ -16,14 +16,12 @@ type echoService struct {
func (e *echoService) ServiceUserID() string { return e.serviceUserID }
func (e *echoService) ServiceID() string { return e.id }
func (e *echoService) ServiceType() string { return "echo" }
func (e *echoService) Plugin(cli *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{
plugin.Command{
Path: []string{"echo"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return &matrix.TextMessage{"m.notice", strings.Join(args, " ")}, nil
},
func (e *echoService) Commands(cli *matrix.Client, roomID string) []types.Command {
return []types.Command{
types.Command{
Path: []string{"echo"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return &matrix.TextMessage{"m.notice", strings.Join(args, " ")}, nil
},
},
}

15
src/github.com/matrix-org/go-neb/services/giphy/giphy.go

@ -13,7 +13,6 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/types"
)
@ -50,14 +49,12 @@ func (s *Service) ServiceUserID() string { return s.serviceUserID }
func (s *Service) ServiceID() string { return s.id }
func (s *Service) ServiceType() string { return ServiceType }
func (s *Service) Plugin(client *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{
plugin.Command{
Path: []string{"giphy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGiphy(client, roomID, userID, args)
},
func (s *Service) Commands(client *matrix.Client, roomID string) []types.Command {
return []types.Command{
types.Command{
Path: []string{"giphy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGiphy(client, roomID, userID, args)
},
},
}

103
src/github.com/matrix-org/go-neb/services/github/github.go

@ -3,17 +3,17 @@ package services
import (
"database/sql"
"fmt"
"regexp"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/realms/github"
"github.com/matrix-org/go-neb/services/github/client"
"github.com/matrix-org/go-neb/types"
"regexp"
"strconv"
"strings"
)
// Matches alphanumeric then a /, then more alphanumeric then a #, then a number.
@ -126,59 +126,60 @@ func (s *githubService) expandIssue(roomID, userID, owner, repo string, issueNum
}
}
func (s *githubService) Plugin(cli *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{
plugin.Command{
Path: []string{"github", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubCreate(roomID, userID, args)
},
func (s *githubService) Commands(cli *matrix.Client, roomID string) []types.Command {
return []types.Command{
types.Command{
Path: []string{"github", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGithubCreate(roomID, userID, args)
},
},
Expansions: []plugin.Expansion{
plugin.Expansion{
Regexp: ownerRepoIssueRegex,
Expand: func(roomID, userID string, matchingGroups []string) interface{} {
// There's an optional group in the regex so matchingGroups can look like:
// [foo/bar#55 foo/bar foo bar 55]
// [#55 55]
if len(matchingGroups) != 5 {
log.WithField("groups", matchingGroups).WithField("len", len(matchingGroups)).Print(
"Unexpected number of groups",
)
}
}
func (s *githubService) Expansions(cli *matrix.Client, roomID string) []types.Expansion {
return []types.Expansion{
types.Expansion{
Regexp: ownerRepoIssueRegex,
Expand: func(roomID, userID string, matchingGroups []string) interface{} {
// There's an optional group in the regex so matchingGroups can look like:
// [foo/bar#55 foo/bar foo bar 55]
// [#55 55]
if len(matchingGroups) != 5 {
log.WithField("groups", matchingGroups).WithField("len", len(matchingGroups)).Print(
"Unexpected number of groups",
)
return nil
}
if matchingGroups[1] == "" && matchingGroups[2] == "" && matchingGroups[3] == "" {
// issue only match, this only works if there is a default repo
defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" {
return nil
}
if matchingGroups[1] == "" && matchingGroups[2] == "" && matchingGroups[3] == "" {
// issue only match, this only works if there is a default repo
defaultRepo := s.defaultRepo(roomID)
if defaultRepo == "" {
return nil
}
segs := strings.Split(defaultRepo, "/")
if len(segs) != 2 {
log.WithFields(log.Fields{
"room_id": roomID,
"default_repo": defaultRepo,
}).Error("Default repo is malformed")
return nil
}
// Fill in the missing fields in matching groups and fall through into ["foo/bar#11", "foo", "bar", "11"]
matchingGroups = []string{
defaultRepo + matchingGroups[0],
defaultRepo,
segs[0],
segs[1],
matchingGroups[4],
}
}
num, err := strconv.Atoi(matchingGroups[4])
if err != nil {
log.WithField("issue_number", matchingGroups[4]).Print("Bad issue number")
segs := strings.Split(defaultRepo, "/")
if len(segs) != 2 {
log.WithFields(log.Fields{
"room_id": roomID,
"default_repo": defaultRepo,
}).Error("Default repo is malformed")
return nil
}
return s.expandIssue(roomID, userID, matchingGroups[2], matchingGroups[3], num)
},
// Fill in the missing fields in matching groups and fall through into ["foo/bar#11", "foo", "bar", "11"]
matchingGroups = []string{
defaultRepo + matchingGroups[0],
defaultRepo,
segs[0],
segs[1],
matchingGroups[4],
}
}
num, err := strconv.Atoi(matchingGroups[4])
if err != nil {
log.WithField("issue_number", matchingGroups[4]).Print("Bad issue number")
return nil
}
return s.expandIssue(roomID, userID, matchingGroups[2], matchingGroups[3], num)
},
},
}

22
src/github.com/matrix-org/go-neb/services/guggy/guggy.go

@ -4,14 +4,14 @@ import (
"bytes"
"encoding/json"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/types"
"io/ioutil"
"math"
"net/http"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/types"
)
type guggyQuery struct {
@ -39,14 +39,12 @@ func (s *guggyService) ServiceUserID() string { return s.serviceUserID }
func (s *guggyService) ServiceID() string { return s.id }
func (s *guggyService) ServiceType() string { return "guggy" }
func (s *guggyService) Plugin(client *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{
plugin.Command{
Path: []string{"guggy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGuggy(client, roomID, userID, args)
},
func (s *guggyService) Commands(client *matrix.Client, roomID string) []types.Command {
return []types.Command{
types.Command{
Path: []string{"guggy"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdGuggy(client, roomID, userID, args)
},
},
}

41
src/github.com/matrix-org/go-neb/services/jira/jira.go

@ -4,19 +4,19 @@ import (
"database/sql"
"errors"
"fmt"
"html"
"net/http"
"regexp"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/andygrunwald/go-jira"
jira "github.com/andygrunwald/go-jira"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"github.com/matrix-org/go-neb/realms/jira"
"github.com/matrix-org/go-neb/realms/jira/urls"
"github.com/matrix-org/go-neb/services/jira/webhook"
"github.com/matrix-org/go-neb/types"
"html"
"net/http"
"regexp"
"strings"
)
// Matches alphas then a -, then a number. E.g "FOO-123"
@ -199,22 +199,23 @@ func (s *jiraService) expandIssue(roomID, userID string, issueKeyGroups []string
)
}
func (s *jiraService) Plugin(cli *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{
plugin.Command{
Path: []string{"jira", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdJiraCreate(roomID, userID, args)
},
func (s *jiraService) Commands(cli *matrix.Client, roomID string) []types.Command {
return []types.Command{
types.Command{
Path: []string{"jira", "create"},
Command: func(roomID, userID string, args []string) (interface{}, error) {
return s.cmdJiraCreate(roomID, userID, args)
},
},
Expansions: []plugin.Expansion{
plugin.Expansion{
Regexp: issueKeyRegex,
Expand: func(roomID, userID string, issueKeyGroups []string) interface{} {
return s.expandIssue(roomID, userID, issueKeyGroups)
},
}
}
func (s *jiraService) Expansions(cli *matrix.Client, roomID string) []types.Expansion {
return []types.Expansion{
types.Expansion{
Regexp: issueKeyRegex,
Expand: func(roomID, userID string, issueKeyGroups []string) interface{} {
return s.expandIssue(roomID, userID, issueKeyGroups)
},
},
}

36
src/github.com/matrix-org/go-neb/types/actions.go

@ -0,0 +1,36 @@
package types
import "regexp"
// A Command is something that a user invokes by sending a message starting with '!'
// followed by a list of strings that name the command, followed by a list of argument
// strings. The argument strings may be quoted using '\"' and '\'' in the same way
// that they are quoted in the unix shell.
type Command struct {
Path []string
Arguments []string
Help string
Command func(roomID, userID string, arguments []string) (content interface{}, err error)
}
// An Expansion is something that actives when the user sends any message
// containing a string matching a given pattern. For example an RFC expansion
// might expand "RFC 6214" into "Adaptation of RFC 1149 for IPv6" and link to
// the appropriate RFC.
type Expansion struct {
Regexp *regexp.Regexp
Expand func(roomID, userID string, matchingGroups []string) interface{}
}
// Matches if the arguments start with the path of the command.
func (command *Command) Matches(arguments []string) bool {
if len(arguments) < len(command.Path) {
return false
}
for i, segment := range command.Path {
if segment != arguments[i] {
return false
}
}
return true
}

56
src/github.com/matrix-org/go-neb/types/auth.go

@ -0,0 +1,56 @@
package types
import (
"encoding/base64"
"encoding/json"
"errors"
"net/http"
)
// AuthRealm represents a place where a user can authenticate themselves.
// This may static (like github.com) or a specific domain (like matrix.org/jira)
type AuthRealm interface {
ID() string
Type() string
Init() error
Register() error
OnReceiveRedirect(w http.ResponseWriter, req *http.Request)
AuthSession(id, userID, realmID string) AuthSession
RequestAuthSession(userID string, config json.RawMessage) interface{}
}
var realmsByType = map[string]func(string, string) AuthRealm{}
// RegisterAuthRealm registers a factory for creating AuthRealm instances.
func RegisterAuthRealm(factory func(string, string) AuthRealm) {
realmsByType[factory("", "").Type()] = factory
}
// CreateAuthRealm creates an AuthRealm of the given type and realm ID.
// Returns an error if the realm couldn't be created or the JSON cannot be unmarshalled.
func CreateAuthRealm(realmID, realmType string, realmJSON []byte) (AuthRealm, error) {
f := realmsByType[realmType]
if f == nil {
return nil, errors.New("Unknown realm type: " + realmType)
}
base64RealmID := base64.RawURLEncoding.EncodeToString([]byte(realmID))
redirectURL := baseURL + "realms/redirects/" + base64RealmID
r := f(realmID, redirectURL)
if err := json.Unmarshal(realmJSON, r); err != nil {
return nil, err
}
if err := r.Init(); err != nil {
return nil, err
}
return r, nil
}
// AuthSession represents a single authentication session between a user and
// an auth realm.
type AuthSession interface {
ID() string
UserID() string
RealmID() string
Authenticated() bool
Info() interface{}
}

66
src/github.com/matrix-org/go-neb/types/types.go → src/github.com/matrix-org/go-neb/types/service.go

@ -4,11 +4,11 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"github.com/matrix-org/go-neb/matrix"
"github.com/matrix-org/go-neb/plugin"
"net/http"
"strings"
"time"
"github.com/matrix-org/go-neb/matrix"
)
// BotOptions for a given bot user in a given room
@ -34,7 +34,8 @@ type Service interface {
ServiceID() string
// Return the type of service. This string MUST NOT change.
ServiceType() string
Plugin(cli *matrix.Client, roomID string) plugin.Plugin
Commands(cli *matrix.Client, roomID string) []Command
Expansions(cli *matrix.Client, roomID string) []Expansion
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client)
// 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
@ -51,9 +52,14 @@ type Service interface {
// DefaultService NO-OPs the implementation of optional Service interface methods. Feel free to override them.
type DefaultService struct{}
// Plugin returns no plugins.
func (s *DefaultService) Plugin(cli *matrix.Client, roomID string) plugin.Plugin {
return plugin.Plugin{}
// Commands returns no commands.
func (s *DefaultService) Commands(cli *matrix.Client, roomID string) []Command {
return []Command{}
}
// Expansions returns no expansions.
func (s *DefaultService) Expansions(cli *matrix.Client, roomID string) []Expansion {
return []Expansion{}
}
// Register does nothing and returns no error.
@ -122,51 +128,3 @@ func CreateService(serviceID, serviceType, serviceUserID string, serviceJSON []b
}
return service, nil
}
// AuthRealm represents a place where a user can authenticate themselves.
// This may static (like github.com) or a specific domain (like matrix.org/jira)
type AuthRealm interface {
ID() string
Type() string
Init() error
Register() error
OnReceiveRedirect(w http.ResponseWriter, req *http.Request)
AuthSession(id, userID, realmID string) AuthSession
RequestAuthSession(userID string, config json.RawMessage) interface{}
}
var realmsByType = map[string]func(string, string) AuthRealm{}
// RegisterAuthRealm registers a factory for creating AuthRealm instances.
func RegisterAuthRealm(factory func(string, string) AuthRealm) {
realmsByType[factory("", "").Type()] = factory
}
// CreateAuthRealm creates an AuthRealm of the given type and realm ID.
// Returns an error if the realm couldn't be created or the JSON cannot be unmarshalled.
func CreateAuthRealm(realmID, realmType string, realmJSON []byte) (AuthRealm, error) {
f := realmsByType[realmType]
if f == nil {
return nil, errors.New("Unknown realm type: " + realmType)
}
base64RealmID := base64.RawURLEncoding.EncodeToString([]byte(realmID))
redirectURL := baseURL + "realms/redirects/" + base64RealmID
r := f(realmID, redirectURL)
if err := json.Unmarshal(realmJSON, r); err != nil {
return nil, err
}
if err := r.Init(); err != nil {
return nil, err
}
return r, nil
}
// AuthSession represents a single authentication session between a user and
// an auth realm.
type AuthSession interface {
ID() string
UserID() string
RealmID() string
Authenticated() bool
Info() interface{}
}
Loading…
Cancel
Save