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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
package connector
import (
"io"
)
/*
A generic connector framework for instant messaging protocols.
Model:
- A connector represents a connection to an outgoing service (IRC, XMPP, etc)
It satisfies a generic interface representing the actions that can be called
(send messages, join room, etc)
- A handler represents a consumer of events happening on a connection
It satisfies a generic interface representing the events that can happend
(message received, rooms autojoined, etc)
- A connector implements a given protocol that has an identifier
Each protocol identifier determines a namespace for user identifiers
and room identifiers which are globally unique for all connections using
this protocol.
For instance, a user can have two IRC conections to different servers.
Internally used user names and room identifiers must contain
the server name to be able to differentiate.
*/
type UserID string
type RoomID string
type Connector interface {
// Set the handler that will receive events happening on this connection
SetHandler(handler Handler)
// Configure (or reconfigure) the connector and attempt to connect
Configure(conf Configuration) error
// Get the identifier of the protocol that is implemented by this connector
Protocol() string
// Get the user id of the connected user
User() UserID
// Set user information (nickname, picture, etc)
SetUserInfo(info *UserInfo) error
// Set room information (name, description, picture, etc)
SetRoomInfo(roomId RoomID, info *RoomInfo) error
// Try to join a channel
// If no error happens, it must fire a Handler.Joined event
Join(roomId RoomID) error
// Try to invite someone to a channel
// Or if roomId == "", just try adding them as friends
Invite(user UserID, roomId RoomID) error
// Leave a channel
Leave(roomId RoomID)
// Search for users
SearchForUsers(query string) ([]UserSearchResult, error)
// Send an event. Returns the ID of the created remote message.
// This ID is used to deduplicate messages: if it comes back, it should have the same Id
// than the one returned here.
// For backends that do not implement IDs (e.g. IRC), an empty string is returned.
// (FIXME how to deduplicate IRC messages?)
// The event that is fed in this function may have its ID already set,
// in which case the backend is free to re-use the ID or select a new one.
Send(event *Event) (string, error)
// Used to send user commands directly
// (first use case: receive 2-factor authentication codes)
UserCommand(string)
// Close the connection
Close()
}
type Handler interface {
// Called to save updated configuration parameters
SaveConfig(config Configuration)
// Called to notify user of stuff going on
SystemMessage(message string)
// Called when a room was joined (automatically or by call to Connector.Join)
Joined(roomId RoomID)
// Called when the user left a room
Left(roomId RoomID)
// Called when a user's info is updated (changed their nickname, status, etc)
// Can also be called with our own user ID when first loaded our user info
UserInfoUpdated(user UserID, info *UserInfo)
// Called when a room's info was updated,
// or the first tome a room's info is retreived
RoomInfoUpdated(roomId RoomID, author UserID, info *RoomInfo)
// Called when an event occurs in a room
// This must not be called for events authored by the user of the connection
Event(event *Event)
// These two functions enable the connector to access a simple key/value
// database to cache some information in order not to generate useless events.
// The connector should function when they are not implemented,
// in which case CacheGet always returns ""
CachePut(key string, value string)
CacheGet(key string) string
}
type EventType int
const (
EVENT_JOIN EventType = iota
EVENT_LEAVE
EVENT_MESSAGE
EVENT_ACTION
)
type Event struct {
Type EventType `json:"type"`
// If non-empty, the event Id is used to deduplicate events in a channel
// This is usefull for backends that provide a backlog of channel messages
// when (re-)joining a room
Id string `json:"id"`
// UserID of the user that sent the event
// If this is a direct message event, this event can only have been authored
// by the user we are talking to (and not by ourself)
Author UserID `json:"author"`
// UserID of the targetted user in the case of a direct message,
// empty if targetting a room
Recipient UserID `json:"recipient"`
// RoomID of the room where the event happenned or of the targetted room,
// or empty string if it happenned by direct message
Room RoomID `json:"room"`
// Message text or action text
Text string `json:"text"`
// Attached files such as images
Attachments []SMediaObject `json:"attachments"`
}
type UserInfo struct {
DisplayName string `json:"display_name"`
// If non-empty, the Filename of the avatar object will be used by Easybridge
// to deduplicate the update events and prevent needless reuploads.
// Example strategy that works for the mattermost backend: use the update timestamp as fictious file name
Avatar SMediaObject `json:"avatar"`
}
type RoomInfo struct {
Name string `json:"name"`
Topic string `json:"topic"`
// Same deduplication comment as for UserInfo.Avatar
Picture SMediaObject `json:"picture"`
}
type UserSearchResult struct {
ID UserID `json:"id"`
DisplayName string `json:"display_name"`
}
type MediaObject interface {
Filename() string
Size() int64
Mimetype() string
// Returns the size of an image if it is an image, otherwise nil
ImageSize() *ImageSize
// Read: must always be implemented
Read() (io.ReadCloser, error)
// URL(): not mandatory, may return an empty string
// If so, Read() is the only way to retrieve the object
URL() string
}
type SMediaObject struct {
MediaObject
}
type ImageSize struct {
Width int `json:"width"`
Height int `json:"height"`
}
|