You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

181 lines
6.6 KiB

package types
import (
"encoding/base64"
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
type GithubOptions struct {
DefaultRepo string `json:"default_repo,omitempty"`
NewIssueLabels []string `json:"new_issue_labels,omitempty"`
}
type BotOptionsContent struct {
Github GithubOptions `json:"github"`
}
// BotOptions for a given bot user in a given room
type BotOptions struct {
RoomID id.RoomID
UserID id.UserID
SetByUserID id.UserID
Options *BotOptionsContent
}
// Poller represents a thing which can poll. Services should implement this method signature to support polling.
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 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.
type Service interface {
// Return the user ID of this service.
ServiceUserID() id.UserID
// Return an opaque ID used to identify this service.
ServiceID() string
// Return the type of service. This string MUST NOT change.
ServiceType() string
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 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
// up resources which are no longer needed (e.g. removing old webhooks).
PostRegister(oldService Service)
}
// DefaultService NO-OPs the implementation of optional Service interface methods. Feel free to override them.
type DefaultService struct {
id string
serviceUserID id.UserID
serviceType string
}
// NewDefaultService creates a new service with implementations for ServiceID(), ServiceType() and ServiceUserID()
func NewDefaultService(serviceID string, serviceUserID id.UserID, serviceType string) DefaultService {
return DefaultService{serviceID, serviceUserID, serviceType}
}
// ServiceID returns the service's ID. In order for this to return the ID, DefaultService MUST have been
// initialised by NewDefaultService, the zero-initialiser is NOT enough.
func (s *DefaultService) ServiceID() string {
return s.id
}
// 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
// is NOT enough.
func (s *DefaultService) ServiceUserID() id.UserID {
return s.serviceUserID
}
// ServiceType returns the type of service. See each individual service package for the ServiceType constant
// to find out what this value actually is. In order for this to return the Type, DefaultService MUST have been
// initialised by NewDefaultService, the zero-initialiser is NOT enough.
func (s *DefaultService) ServiceType() string {
return s.serviceType
}
// Commands returns no commands.
func (s *DefaultService) Commands(cli MatrixClient) []Command {
return []Command{}
}
// Expansions returns no expansions.
func (s *DefaultService) Expansions(cli MatrixClient) []Expansion {
return []Expansion{}
}
// Register does nothing and returns no error.
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 MatrixClient) {
w.WriteHeader(200) // Do nothing
}
var baseURL = ""
// BaseURL sets the base URL of NEB to the url given. This URL must be accessible from the
// public internet.
func BaseURL(u string) error {
if u == "" {
return errors.New("BASE_URL not found")
}
if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
return errors.New("BASE_URL must start with http[s]://")
}
if !strings.HasSuffix(u, "/") {
u = u + "/"
}
baseURL = u
return nil
}
var servicesByType = map[string]func(string, id.UserID, string) Service{}
var serviceTypesWhichPoll = map[string]bool{}
// RegisterService registers a factory for creating Service instances.
func RegisterService(factory func(string, id.UserID, string) Service) {
s := factory("", "", "")
servicesByType[s.ServiceType()] = factory
if _, ok := s.(Poller); ok {
serviceTypesWhichPoll[s.ServiceType()] = true
}
}
// PollingServiceTypes returns a list of service types which meet the Poller interface
func PollingServiceTypes() (types []string) {
for t := range serviceTypesWhichPoll {
types = append(types, t)
}
return
}
// CreateService creates a Service of the given type and serviceID.
// Returns an error if the Service couldn't be created.
func CreateService(serviceID, serviceType string, serviceUserID id.UserID, serviceJSON []byte) (Service, error) {
f := servicesByType[serviceType]
if f == nil {
return nil, errors.New("Unknown service type: " + serviceType)
}
base64ServiceID := base64.RawURLEncoding.EncodeToString([]byte(serviceID))
webhookEndpointURL := baseURL + "services/hooks/" + base64ServiceID
service := f(serviceID, serviceUserID, webhookEndpointURL)
if err := json.Unmarshal(serviceJSON, service); err != nil {
return nil, err
}
return service, nil
}