2018-06-18 22:25:47 +00:00
|
|
|
// maubot - A plugin-based Matrix bot system written in Go.
|
|
|
|
// Copyright (C) 2018 Tulir Asokan
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package maubot
|
|
|
|
|
2018-06-20 19:25:33 +00:00
|
|
|
type CommandHandler func(*Event) CommandHandlerResult
|
2018-06-18 22:25:47 +00:00
|
|
|
|
|
|
|
type CommandSpec struct {
|
2018-06-20 19:25:33 +00:00
|
|
|
Commands []Command `json:"commands,omitempty"`
|
|
|
|
PassiveCommands []PassiveCommand `json:"passive_commands,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (spec *CommandSpec) Clone() *CommandSpec {
|
|
|
|
return &CommandSpec{
|
|
|
|
Commands: append([]Command(nil), spec.Commands...),
|
|
|
|
PassiveCommands: append([]PassiveCommand(nil), spec.PassiveCommands...),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (spec *CommandSpec) Merge(otherSpecs ...*CommandSpec) {
|
|
|
|
for _, otherSpec := range otherSpecs {
|
|
|
|
spec.Commands = append(spec.Commands, otherSpec.Commands...)
|
|
|
|
spec.PassiveCommands = append(spec.PassiveCommands, otherSpec.PassiveCommands...)
|
|
|
|
}
|
2018-06-18 22:25:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (spec *CommandSpec) Equals(otherSpec *CommandSpec) bool {
|
2018-06-20 19:25:33 +00:00
|
|
|
if otherSpec == nil ||
|
|
|
|
len(spec.Commands) != len(otherSpec.Commands) ||
|
|
|
|
len(spec.PassiveCommands) != len(otherSpec.PassiveCommands) {
|
2018-06-18 22:25:47 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for index, cmd := range spec.Commands {
|
|
|
|
otherCmd := otherSpec.Commands[index]
|
|
|
|
if !cmd.Equals(otherCmd) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for index, cmd := range spec.PassiveCommands {
|
|
|
|
otherCmd := otherSpec.PassiveCommands[index]
|
|
|
|
if !cmd.Equals(otherCmd) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type Command struct {
|
|
|
|
Syntax string `json:"syntax"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
Arguments ArgumentMap `json:"arguments"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cmd Command) Equals(otherCmd Command) bool {
|
|
|
|
return cmd.Syntax == otherCmd.Syntax &&
|
|
|
|
cmd.Description == otherCmd.Description &&
|
|
|
|
cmd.Arguments.Equals(otherCmd.Arguments)
|
|
|
|
}
|
|
|
|
|
|
|
|
type ArgumentMap map[string]Argument
|
|
|
|
|
|
|
|
func (argMap ArgumentMap) Equals(otherMap ArgumentMap) bool {
|
|
|
|
if len(argMap) != len(otherMap) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, argument := range argMap {
|
|
|
|
otherArgument, ok := otherMap[name]
|
|
|
|
if !ok || !argument.Equals(otherArgument) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type Argument struct {
|
|
|
|
Matches string `json:"matches"`
|
|
|
|
Required bool `json:"required"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (arg Argument) Equals(otherArg Argument) bool {
|
|
|
|
return arg.Matches == otherArg.Matches &&
|
|
|
|
arg.Required == otherArg.Required &&
|
|
|
|
arg.Description == otherArg.Description
|
|
|
|
}
|
|
|
|
|
|
|
|
// Common PassiveCommand MatchAgainst targets.
|
|
|
|
const (
|
|
|
|
MatchAgainstBody = "body"
|
|
|
|
)
|
|
|
|
|
2018-09-19 22:16:13 +00:00
|
|
|
// JSONLeftEquals checks if the given JSON-parsed interfaces are equal.
|
|
|
|
// Extra properties in the right interface are ignored.
|
|
|
|
func JSONLeftEquals(left, right interface{}) bool {
|
|
|
|
switch val := left.(type) {
|
|
|
|
case nil:
|
|
|
|
return right == nil
|
|
|
|
case bool:
|
|
|
|
rightVal, ok := right.(bool)
|
|
|
|
return ok && rightVal
|
|
|
|
case float64:
|
|
|
|
rightVal, ok := right.(float64)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return val == rightVal
|
|
|
|
case string:
|
|
|
|
rightVal, ok := right.(string)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return val == rightVal
|
|
|
|
case []interface{}:
|
|
|
|
rightVal, ok := right.([]interface{})
|
|
|
|
if !ok || len(val) != len(rightVal) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for index, leftChild := range val {
|
|
|
|
rightChild := rightVal[index]
|
|
|
|
if !JSONLeftEquals(leftChild, rightChild) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case map[string]interface{}:
|
|
|
|
rightVal, ok := right.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for key, leftChild := range val {
|
|
|
|
rightChild, ok := rightVal[key]
|
|
|
|
if !ok || !JSONLeftEquals(leftChild, rightChild) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-06-18 22:25:47 +00:00
|
|
|
type PassiveCommand struct {
|
2018-09-19 22:16:13 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Matches string `json:"matches"`
|
|
|
|
MatchAgainst string `json:"match_against"`
|
|
|
|
MatchEvent interface{} `json:"match_event"`
|
2018-06-18 22:25:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cmd PassiveCommand) Equals(otherCmd PassiveCommand) bool {
|
|
|
|
return cmd.Name == otherCmd.Name &&
|
|
|
|
cmd.Matches == otherCmd.Matches &&
|
|
|
|
cmd.MatchAgainst == otherCmd.MatchAgainst &&
|
2018-09-19 22:16:13 +00:00
|
|
|
(cmd.MatchEvent != nil && JSONLeftEquals(cmd.MatchEvent, otherCmd.MatchEvent) || otherCmd.MatchEvent == nil)
|
2018-06-18 22:25:47 +00:00
|
|
|
}
|