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

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