This repository was archived by the owner on Jun 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 92
/
Copy pathservice.go
181 lines (155 loc) · 6.59 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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
}