aboutsummaryrefslogtreecommitdiff
path: root/connector/connector.go
blob: 0819bd7abf0988a5d915ad5ad30d5e50ef947fdd (plain) (blame)
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"`
}