-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnctalkclient.js
315 lines (257 loc) · 11 KB
/
nctalkclient.js
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
const EventEmitter = require("events");
const https = require("https");
const http = require("http");
const TalkAccess = require("./nctalkaccess");
const TalkCapabilities = require("./nctalkcapabilities");
const TalkRooms = require("./nctalkrooms");
const TalkConversation = require("./nctalkconversation");
const packageDetails = require('./package.json');
// Test Code
// const private = require("../.private_keys.js");
// const nchttp = new TalkAccess(https, {
// server: private.talkcredentials.server,
// user: private.talkcredentials.user,
// pass: private.talkcredentials.pass,
// port: private.talkcredentials.port
// });
// const capabilities = new TalkCapabilities(nchttp);
// capabilities.get(() => {
// this.DebugLog("get capabilities done");
// const roomlist = new TalkRooms(nchttp, capabilities);
// roomlist.get(() => {
// this.DebugLog("get roomlist done");
// const conversation = new TalkConversation(nchttp, capabilities, roomlist.roomlist[2]);
// conversation.SendMessage("DIES IST EIN TEST", () => {
// this.DebugLog("SendMessage done")
// conversation.SetListenMode(true);
// conversation.WaitNewMessages((messages) => {
// this.DebugLog(messages);
// });
// });
// });
// });
/*
TODO:
Add timemout configuration parameter for nextcloud talk timeout (heartbeat) and http connection timeout
Ideas for the future:
group list attribute for every room to send multicast message to multiple rooms belonging to one group
smarthome user gets a set of "botfather" functions as an alternative (password protected) way to configure this adapter - to create/organize new rooms/groups
Add filter to allow only nctalk group and/or 1to1 conversation types to be Used
New 1to1/group conversation will automatically be joined (ListenModeActive=true) either password protected or after request sent to the nextcloud talk admin was approved
*/
class Talkclient extends EventEmitter {
constructor(options) {
super();
this.state = "INIT";
this.state_info = undefined;
this.on("Eventloop", this._Eventloop);
// Todo: Add check for options
this.server = options.server;
this.user = options.user;
this.pass = options.pass;
this.port = options.port;
this.debug = options.debug;
if(this.port == 80) {
this.nchttp = new TalkAccess(http, {
server: options.server,
user: options.user,
pass: options.pass,
port: options.port
});
} else {
this.nchttp = new TalkAccess(https, {
server: options.server,
user: options.user,
pass: options.pass,
port: options.port
});
}
}
GetVersion() {
return packageDetails.version;
}
start(delay) {
//this.emit("Eventloop","START");
this._EventloopTrigger(`START nctalkclient ${packageDetails.version}`, delay);
}
GetOwnActorIdLowerCase() {
// actorId = user name - haven't found anything about this in the nextcloud spreed (talk) documenation
return this.user.toLowerCase();
}
RoomListenMode(token, active) {
const conversation = this._GetConversionfromToken(token);
if (conversation)
conversation.SetListenMode(active);
}
SendMessage(token, msg) {
const conversation = this._GetConversionfromToken(token);
if (conversation) {
conversation.SendMessage(msg, () => {
this.DebugLog("SendMessage done");
});
}
}
ShareFile(token, filename) {
const conversation = this._GetConversionfromToken(token);
if (conversation) {
conversation.ShareFile(filename, () => {
this.DebugLog("ShareFile done");
});
}
}
ErrorLog(msg) {
//global.ErrorLog(msg);
this.emit("Error", msg);
}
DebugLog(msg) {
//global.DebugLog(msg);
if (this.debug == true) {
this.emit("Debug", msg);
}
}
/* @param {string} text - Text to send
* @param {boolean|int} [asReply] - Defaults to true. Sends Text as Reply (with quote)
* If int: Sends only if X new messages in between
* @returns {fetch}
*/
// message.reply = (text, asReply) => {
// if (message.isReplyable && asReply !== false) {
// if (Number.isSafeInteger(asReply)) {
// const diff = channel.lastKnownMessageId - message.id;
// return this.sendText(text, message.token, diff >= asReply ? message.id : 0);
// } else {
// return this.sendText(text, message.token, message.id);
// }
// }
_GetConversionfromToken(token) {
for (const idx_c in this.conversation) {
if (this.conversation[idx_c].roominfo.token == token)
return this.conversation[idx_c];
}
return undefined;
}
_CreateConversation() {
// for each element in listofrooms create Conversation Obj
// An option would be to move this to the rooms instance
// but as the plan is to create talk independet group instances with flexible 1to1 and 1tomany communication
// idea here is that
// we will see how to best handle this here
const listofrooms = this.rooms.getlistofrooms();
for (const key in listofrooms) {
this.conversation[key] = new TalkConversation(this, this.nchttp, this.capabilities, listofrooms[key]);
}
}
_EventloopTrigger(event, timeout) {
if (timeout) {
setTimeout((function (event) {
this.emit("Eventloop", event);
}).bind(this), timeout, event);
} else {
this.emit("Eventloop", event);
}
}
// Statemachine eventloop
_Eventloop(event) {
this.DebugLog(event);
// QUESTION: In case of an error do the complete sequence ? in the unlikely case nextcloud was updated and API version changed ?
// QUESTION: Check in the background for new rooms and automaticlly add and join them?
// State machine handles startup and receiving new messages of active marked conversations
switch (this.state) {
case "INIT":
// First get capabilities - needed to get supported API version and build proper urls
this.DebugLog("Get capabilities");
this.state = "WAIT";
this.capabilities = new TalkCapabilities(this.nchttp);
this.capabilities.get((retcode, res) => {
this.DebugLog(res.body);
if (retcode == "OK") {
this.state = "CAPABILITIES_DONE";
}
else {
this.state = "ERROR";
this.state_info = { retcode: retcode, res: res };
}
this._EventloopTrigger("Get capabilities done");
});
break;
case "CAPABILITIES_DONE":
// Once Capabilites are done - let's get the rooms/conversations of the user
this.DebugLog("Get Rooms");
this.state = "WAIT";
// check for pre-condition capabilities are available
if (this.capabilities === undefined) {
this.state = "ERROR";
this.state_info = "Get Rooms - capabilities missing";
this._EventloopTrigger("Get rooms done");
break;
}
this.rooms = new TalkRooms(this.nchttp, this.capabilities);
this.rooms.fetch((retcode, res) => {
this.DebugLog(res.body);
if (retcode == "OK") {
this.conversation = [];
// We have the infos about all chat rooms of the smarthome user
// now create conversation instances which handles waitmsg/sendmsg
this._CreateConversation();
this.state = "WAIT_CHAT_MSG";
this.emit("Ready", this.rooms.getlistofrooms());
}
else {
this.state = "ERROR";
this.state_info = { retcode: retcode, res: res };
}
this._EventloopTrigger("Get rooms done");
});
break;
case "WAIT_CHAT_MSG":
for (const idx_c in this.conversation) {
this.conversation[idx_c].WaitNewMessages((retcode, res) => {
if (retcode == "OK") {
if (res !== undefined) {
// New message arrived
this.DebugLog(res);
this.emit("Message_" + this.conversation[idx_c].roominfo.token, res);
this._EventloopTrigger("WaitNewMessages done");
} else {
this.ErrorLog("WaitNewMessages OK but res is undefined!");
this.state = "ERROR";
this.state_info = { retcode: retcode, res: res };
this._EventloopTrigger("WaitNewMessages unkown Error");
}
}
else if (retcode == "NOMSG") {
this.DebugLog(res);
this._EventloopTrigger("WaitNewMessages done");
} else if (retcode == "ERROR") {
// Don't get in ERROR state report it and retry until connection is back or aborted
// Typically two types of error - server is not reachable
// or nextcloud talk didn't send any heartbeat usually every 30sec
this.ErrorLog(res);
this.emit("Error", res);
this._EventloopTrigger("WaitNewMessages Error", 30000);
} else {
this.state = "ERROR";
this.state_info = { retcode: retcode, res: res };
this._EventloopTrigger("WaitNewMessages unknown Error");
}
});
}
break;
case "WAIT":
// WAIT Do nothing
this.DebugLog("WAIT");
break;
case "ERROR":
// Report Error then do nothing
this.emit("Error", this.state_info);
this.state = "END";
break;
case "END":
// Do nothing
break;
default:
break;
}
}
}
module.exports = Talkclient;