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.

172 lines
6.4 KiB

  1. package types
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "strings"
  8. "time"
  9. "maunium.net/go/mautrix"
  10. "maunium.net/go/mautrix/event"
  11. "maunium.net/go/mautrix/id"
  12. )
  13. // BotOptions for a given bot user in a given room
  14. type BotOptions struct {
  15. RoomID id.RoomID
  16. UserID id.UserID
  17. SetByUserID id.UserID
  18. Options map[string]interface{}
  19. }
  20. // Poller represents a thing which can poll. Services should implement this method signature to support polling.
  21. type Poller interface {
  22. // OnPoll is called when the poller should poll. Return the timestamp when you want to be polled again.
  23. // Return 0 to never be polled again.
  24. OnPoll(client MatrixClient) time.Time
  25. }
  26. // MatrixClient represents an object that can communicate with a Matrix server in certain ways that services require.
  27. type MatrixClient interface {
  28. // Join a room by ID or alias. Content can optionally specify the request body.
  29. JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *mautrix.RespJoinRoom, err error)
  30. // Send a message event to a room.
  31. SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{},
  32. extra ...mautrix.ReqSendEvent) (resp *mautrix.RespSendEvent, err error)
  33. // Upload an HTTP URL.
  34. UploadLink(link string) (*mautrix.RespMediaUpload, error)
  35. }
  36. // A Service is the configuration for a bot service.
  37. type Service interface {
  38. // Return the user ID of this service.
  39. ServiceUserID() id.UserID
  40. // Return an opaque ID used to identify this service.
  41. ServiceID() string
  42. // Return the type of service. This string MUST NOT change.
  43. ServiceType() string
  44. Commands(cli MatrixClient) []Command
  45. Expansions(cli MatrixClient) []Expansion
  46. OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli MatrixClient)
  47. // A lifecycle function which is invoked when the service is being registered. The old service, if one exists, is provided,
  48. // along with a Client instance for ServiceUserID(). If this function returns an error, the service will not be registered
  49. // or persisted to the database, and the user's request will fail. This can be useful if you depend on external factors
  50. // such as registering webhooks.
  51. Register(oldService Service, client MatrixClient) error
  52. // A lifecycle function which is invoked after the service has been successfully registered and persisted to the database.
  53. // This function is invoked within the critical section for configuring services, guaranteeing that there will not be
  54. // concurrent modifications to this service whilst this function executes. This lifecycle hook should be used to clean
  55. // up resources which are no longer needed (e.g. removing old webhooks).
  56. PostRegister(oldService Service)
  57. }
  58. // DefaultService NO-OPs the implementation of optional Service interface methods. Feel free to override them.
  59. type DefaultService struct {
  60. id string
  61. serviceUserID id.UserID
  62. serviceType string
  63. }
  64. // NewDefaultService creates a new service with implementations for ServiceID(), ServiceType() and ServiceUserID()
  65. func NewDefaultService(serviceID string, serviceUserID id.UserID, serviceType string) DefaultService {
  66. return DefaultService{serviceID, serviceUserID, serviceType}
  67. }
  68. // ServiceID returns the service's ID. In order for this to return the ID, DefaultService MUST have been
  69. // initialised by NewDefaultService, the zero-initialiser is NOT enough.
  70. func (s *DefaultService) ServiceID() string {
  71. return s.id
  72. }
  73. // ServiceUserID returns the user ID that the service sends events as. In order for this to return the
  74. // service user ID, DefaultService MUST have been initialised by NewDefaultService, the zero-initialiser
  75. // is NOT enough.
  76. func (s *DefaultService) ServiceUserID() id.UserID {
  77. return s.serviceUserID
  78. }
  79. // ServiceType returns the type of service. See each individual service package for the ServiceType constant
  80. // to find out what this value actually is. In order for this to return the Type, DefaultService MUST have been
  81. // initialised by NewDefaultService, the zero-initialiser is NOT enough.
  82. func (s *DefaultService) ServiceType() string {
  83. return s.serviceType
  84. }
  85. // Commands returns no commands.
  86. func (s *DefaultService) Commands(cli MatrixClient) []Command {
  87. return []Command{}
  88. }
  89. // Expansions returns no expansions.
  90. func (s *DefaultService) Expansions(cli MatrixClient) []Expansion {
  91. return []Expansion{}
  92. }
  93. // Register does nothing and returns no error.
  94. func (s *DefaultService) Register(oldService Service, cli MatrixClient) error { return nil }
  95. // PostRegister does nothing.
  96. func (s *DefaultService) PostRegister(oldService Service) {}
  97. // OnReceiveWebhook does nothing but 200 OK the request.
  98. func (s *DefaultService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli MatrixClient) {
  99. w.WriteHeader(200) // Do nothing
  100. }
  101. var baseURL = ""
  102. // BaseURL sets the base URL of NEB to the url given. This URL must be accessible from the
  103. // public internet.
  104. func BaseURL(u string) error {
  105. if u == "" {
  106. return errors.New("BASE_URL not found")
  107. }
  108. if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
  109. return errors.New("BASE_URL must start with http[s]://")
  110. }
  111. if !strings.HasSuffix(u, "/") {
  112. u = u + "/"
  113. }
  114. baseURL = u
  115. return nil
  116. }
  117. var servicesByType = map[string]func(string, id.UserID, string) Service{}
  118. var serviceTypesWhichPoll = map[string]bool{}
  119. // RegisterService registers a factory for creating Service instances.
  120. func RegisterService(factory func(string, id.UserID, string) Service) {
  121. s := factory("", "", "")
  122. servicesByType[s.ServiceType()] = factory
  123. if _, ok := s.(Poller); ok {
  124. serviceTypesWhichPoll[s.ServiceType()] = true
  125. }
  126. }
  127. // PollingServiceTypes returns a list of service types which meet the Poller interface
  128. func PollingServiceTypes() (types []string) {
  129. for t := range serviceTypesWhichPoll {
  130. types = append(types, t)
  131. }
  132. return
  133. }
  134. // CreateService creates a Service of the given type and serviceID.
  135. // Returns an error if the Service couldn't be created.
  136. func CreateService(serviceID, serviceType string, serviceUserID id.UserID, serviceJSON []byte) (Service, error) {
  137. f := servicesByType[serviceType]
  138. if f == nil {
  139. return nil, errors.New("Unknown service type: " + serviceType)
  140. }
  141. base64ServiceID := base64.RawURLEncoding.EncodeToString([]byte(serviceID))
  142. webhookEndpointURL := baseURL + "services/hooks/" + base64ServiceID
  143. service := f(serviceID, serviceUserID, webhookEndpointURL)
  144. if err := json.Unmarshal(serviceJSON, service); err != nil {
  145. return nil, err
  146. }
  147. return service, nil
  148. }