@sockethub/platform-irc 4.0.0-alpha.3 → 4.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +123 -77
- package/README.md +68 -73
- package/package.json +17 -22
- package/src/index.test.data.ts +60 -0
- package/src/index.test.ts +277 -0
- package/src/index.ts +764 -0
- package/src/octal-hack.js +4 -0
- package/src/schema.ts +66 -0
- package/src/types.ts +18 -0
- package/typedoc.json +11 -0
- package/index.js +0 -701
- package/index.test.data.js +0 -61
- package/index.test.js +0 -204
package/index.js
DELETED
|
@@ -1,701 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is a platform for Sockethub implementing IRC functionality.
|
|
3
|
-
*
|
|
4
|
-
* Developed by Nick Jennings (https://github.com/silverbucket)
|
|
5
|
-
*
|
|
6
|
-
* Sockethub is licensed under the LGPLv3.
|
|
7
|
-
* See the LICENSE file for details.
|
|
8
|
-
*
|
|
9
|
-
* The latest version of this module can be found here:
|
|
10
|
-
* git://github.com/sockethub/sockethub.git
|
|
11
|
-
*
|
|
12
|
-
* For more information about Sockethub visit http://sockethub.org/.
|
|
13
|
-
*
|
|
14
|
-
* This program is distributed in the hope that it will be useful,
|
|
15
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
if (typeof (NetSocket) !== 'object') {
|
|
20
|
-
net = require('net');
|
|
21
|
-
}
|
|
22
|
-
if (typeof (TlsSocket) !== 'object') {
|
|
23
|
-
tls = require('tls');
|
|
24
|
-
}
|
|
25
|
-
if (typeof (IrcSocket) !== 'object') {
|
|
26
|
-
IrcSocket = require('irc-socket-sasl');
|
|
27
|
-
}
|
|
28
|
-
if (typeof (IRC2AS) !== 'object') {
|
|
29
|
-
IRC2AS = require('@sockethub/irc2as');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @class IRC
|
|
35
|
-
* @constructor
|
|
36
|
-
*
|
|
37
|
-
* @description
|
|
38
|
-
* Handles all actions related to communication via. the IRC protocol.
|
|
39
|
-
*
|
|
40
|
-
* Uses the `irc-factory` node module as a base tool for interacting with IRC.
|
|
41
|
-
*
|
|
42
|
-
* {@link https://github.com/ircanywhere/irc-factory}
|
|
43
|
-
*
|
|
44
|
-
* @param {object} cfg a unique config object for this instance // TODO LINK
|
|
45
|
-
*/
|
|
46
|
-
function IRC(cfg) {
|
|
47
|
-
cfg = (typeof cfg === 'object') ? cfg : {};
|
|
48
|
-
this.debug = cfg.debug;
|
|
49
|
-
this.sendToClient = cfg.sendToClient;
|
|
50
|
-
this.updateActor = cfg.updateActor;
|
|
51
|
-
this.__forceDisconnect = false;
|
|
52
|
-
this.__clientConnecting = false;
|
|
53
|
-
this.__client = undefined;
|
|
54
|
-
this.__jobQueue = []; // list of handlers to confirm when message delivery confirmed
|
|
55
|
-
this.__channels = new Set();
|
|
56
|
-
this.__handledActors = new Set();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Property: schema
|
|
61
|
-
*
|
|
62
|
-
* @description
|
|
63
|
-
* JSON schema defining the types this platform accepts.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* In the below example, Sockethub will validate the incoming credentials object
|
|
67
|
-
* against whatever is defined in the `credentials` portion of the schema
|
|
68
|
-
* object.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* It will also check if the incoming AS object uses a type which exists in the
|
|
72
|
-
* `types` portion of the schema object (should be an array of type names).
|
|
73
|
-
*
|
|
74
|
-
* * **NOTE**: For more information on using the credentials object from a client,
|
|
75
|
-
* see [Sockethub Client](https://github.com/sockethub/sockethub/wiki/Sockethub-Client)
|
|
76
|
-
*
|
|
77
|
-
* Valid AS object for setting IRC credentials:
|
|
78
|
-
* @example
|
|
79
|
-
*
|
|
80
|
-
* {
|
|
81
|
-
* type: 'credentials',
|
|
82
|
-
* context: 'irc',
|
|
83
|
-
* actor: {
|
|
84
|
-
* id: 'testuser@irc.host.net',
|
|
85
|
-
* type: 'person',
|
|
86
|
-
* name: 'Mr. Test User',
|
|
87
|
-
* userName: 'testuser'
|
|
88
|
-
* },
|
|
89
|
-
* object: {
|
|
90
|
-
* type: 'credentials',
|
|
91
|
-
* server: 'irc.host.net',
|
|
92
|
-
* nick: 'testuser',
|
|
93
|
-
* password: 'asdasdasdasd',
|
|
94
|
-
* port: 6697,
|
|
95
|
-
* secure: true,
|
|
96
|
-
* sasl: true
|
|
97
|
-
* }
|
|
98
|
-
* }
|
|
99
|
-
*/
|
|
100
|
-
IRC.prototype.schema = {
|
|
101
|
-
"name": "irc",
|
|
102
|
-
"version": require('./package.json').version,
|
|
103
|
-
"messages" : {
|
|
104
|
-
"required": [ 'type' ],
|
|
105
|
-
"properties": {
|
|
106
|
-
"type": {
|
|
107
|
-
"enum": [ 'connect', 'update', 'join', 'leave', 'send', 'query', 'announce' ]
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
"credentials" : {
|
|
112
|
-
"required": [ 'object' ],
|
|
113
|
-
"properties": {
|
|
114
|
-
// TODO platforms shouldn't have to define the actor property
|
|
115
|
-
// if they don't want to, just credential specifics
|
|
116
|
-
"actor": {
|
|
117
|
-
"type": "object",
|
|
118
|
-
"required": [ "id" ]
|
|
119
|
-
},
|
|
120
|
-
"object": {
|
|
121
|
-
"type": "object",
|
|
122
|
-
"required": [ 'type', 'nick', 'server' ],
|
|
123
|
-
"additionalProperties": false,
|
|
124
|
-
"properties" : {
|
|
125
|
-
"type": {
|
|
126
|
-
"type": "string"
|
|
127
|
-
},
|
|
128
|
-
"nick" : {
|
|
129
|
-
"type": "string"
|
|
130
|
-
},
|
|
131
|
-
"username" : {
|
|
132
|
-
"type": "string"
|
|
133
|
-
},
|
|
134
|
-
"password" : {
|
|
135
|
-
"type": "string"
|
|
136
|
-
},
|
|
137
|
-
"server" : {
|
|
138
|
-
"type": "string"
|
|
139
|
-
},
|
|
140
|
-
"port" : {
|
|
141
|
-
"type": "number"
|
|
142
|
-
},
|
|
143
|
-
"secure": {
|
|
144
|
-
"type": "boolean"
|
|
145
|
-
},
|
|
146
|
-
"sasl": {
|
|
147
|
-
"type": "boolean"
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
IRC.prototype.config = {
|
|
157
|
-
persist: true,
|
|
158
|
-
requireCredentials: [ 'connect', 'update' ],
|
|
159
|
-
initialized: false
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Function: connect
|
|
164
|
-
*
|
|
165
|
-
* Conenct to an IRC server.
|
|
166
|
-
*
|
|
167
|
-
* @param {object} job activity streams object
|
|
168
|
-
* @param {object} credentials credentials object
|
|
169
|
-
* @param {object} done callback when job is done
|
|
170
|
-
*/
|
|
171
|
-
|
|
172
|
-
IRC.prototype.connect = function (job, credentials, done) {
|
|
173
|
-
this.__getClient(job.actor.id, credentials, (err, client) => {
|
|
174
|
-
if (err) { return done(err); }
|
|
175
|
-
return done();
|
|
176
|
-
});
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Function: join
|
|
181
|
-
*
|
|
182
|
-
* Join a room or private conversation.
|
|
183
|
-
*
|
|
184
|
-
* @param {object} job activity streams object // TODO LINK
|
|
185
|
-
* @param {object} done callback when job is done // TODO LINK
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
*
|
|
189
|
-
* {
|
|
190
|
-
* context: 'irc',
|
|
191
|
-
* type: 'join',
|
|
192
|
-
* actor: {
|
|
193
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
194
|
-
* type: 'person',
|
|
195
|
-
* name: 'slvrbckt'
|
|
196
|
-
* },
|
|
197
|
-
* target: {
|
|
198
|
-
* id: 'irc.freenode.net/a-room',
|
|
199
|
-
* type: 'room',
|
|
200
|
-
* name: '#a-room'
|
|
201
|
-
* },
|
|
202
|
-
* object: {}
|
|
203
|
-
* }
|
|
204
|
-
*
|
|
205
|
-
*/
|
|
206
|
-
IRC.prototype.join = function (job, done) {
|
|
207
|
-
this.debug('join() called for ' + job.actor.id);
|
|
208
|
-
this.__getClient(job.actor.id, false, (err, client) => {
|
|
209
|
-
if (err) { return done(err); }
|
|
210
|
-
if (this.__channels.has(job.target.name)) {
|
|
211
|
-
this.debug(`channel ${job.target.name} already joined`);
|
|
212
|
-
return done();
|
|
213
|
-
}
|
|
214
|
-
// join channel
|
|
215
|
-
this.__jobQueue.push(() => {
|
|
216
|
-
this.__hasJoined(job.target.name);
|
|
217
|
-
done();
|
|
218
|
-
});
|
|
219
|
-
this.debug('sending join ' + job.target.name);
|
|
220
|
-
client.raw(['JOIN', job.target.name]);
|
|
221
|
-
client.raw('PING ' + job.actor.name);
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Function leave
|
|
227
|
-
*
|
|
228
|
-
* Leave a room or private conversation.
|
|
229
|
-
*
|
|
230
|
-
* @param {object} job activity streams object // TODO LINK
|
|
231
|
-
* @param {object} done callback when job is done // TODO LINK
|
|
232
|
-
*
|
|
233
|
-
* @example
|
|
234
|
-
* {
|
|
235
|
-
* context: 'irc',
|
|
236
|
-
* type: 'leave',
|
|
237
|
-
* actor: {
|
|
238
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
239
|
-
* type: 'person',
|
|
240
|
-
* name: 'slvrbckt'
|
|
241
|
-
* },
|
|
242
|
-
* target: {
|
|
243
|
-
* id: 'irc.freenode.net/remotestorage',
|
|
244
|
-
* type: 'room',
|
|
245
|
-
* name: '#remotestorage'
|
|
246
|
-
* },
|
|
247
|
-
* object: {}
|
|
248
|
-
* }
|
|
249
|
-
*
|
|
250
|
-
*/
|
|
251
|
-
IRC.prototype.leave = function (job, done) {
|
|
252
|
-
this.debug('leave() called for ' + job.actor.name);
|
|
253
|
-
this.__getClient(job.actor.id, false, (err, client) => {
|
|
254
|
-
if (err) { return done(err); }
|
|
255
|
-
// leave channel
|
|
256
|
-
this.__hasLeft(job.target.name);
|
|
257
|
-
client.raw(['PART', job.target.name]);
|
|
258
|
-
done();
|
|
259
|
-
});
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Function: send
|
|
264
|
-
*
|
|
265
|
-
* Send a message to a room or private conversation.
|
|
266
|
-
*
|
|
267
|
-
* @param {object} job activity streams object // TODO LINK
|
|
268
|
-
* @param {object} done callback when job is done // TODO LINK
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
*
|
|
272
|
-
* {
|
|
273
|
-
* context: 'irc',
|
|
274
|
-
* type: 'send',
|
|
275
|
-
* actor: {
|
|
276
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
277
|
-
* type: 'person',
|
|
278
|
-
* name: 'Nick Jennings',
|
|
279
|
-
* userName: 'slvrbckt'
|
|
280
|
-
* },
|
|
281
|
-
* target: {
|
|
282
|
-
* id: 'irc.freenode.net/remotestorage',
|
|
283
|
-
* type: 'room',
|
|
284
|
-
* name: '#remotestorage'
|
|
285
|
-
* },
|
|
286
|
-
* object: {
|
|
287
|
-
* type: 'message',
|
|
288
|
-
* content: 'Hello from Sockethub!'
|
|
289
|
-
* }
|
|
290
|
-
* }
|
|
291
|
-
*
|
|
292
|
-
*/
|
|
293
|
-
IRC.prototype.send = function (job, done) {
|
|
294
|
-
this.debug('send() called for ' + job.actor.id + ' target: ' + job.target.id);
|
|
295
|
-
this.__getClient(job.actor.id, false, (err, client) => {
|
|
296
|
-
if (err) { return done(err); }
|
|
297
|
-
|
|
298
|
-
if (typeof job.object.content !== 'string') {
|
|
299
|
-
return done('cannot send message with no object.content');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
let msg = job.object.content.trim();
|
|
303
|
-
if (msg.indexOf('/') === 0) {
|
|
304
|
-
// message intented as command
|
|
305
|
-
msg += ' ';
|
|
306
|
-
const cmd = msg.substr(0, msg.indexOf(' ')).substr(1).toUpperCase(); // get command
|
|
307
|
-
msg = msg.substr(msg.indexOf(' ') + 1).replace(/\s\s*$/, ''); // remove command from message
|
|
308
|
-
if (cmd === 'ME') {
|
|
309
|
-
// handle /me messages uniquely
|
|
310
|
-
job.object.type = 'me';
|
|
311
|
-
job.object.content = msg;
|
|
312
|
-
} else if (cmd === 'NOTICE') {
|
|
313
|
-
// attempt to send as raw command
|
|
314
|
-
job.object.type = 'notice';
|
|
315
|
-
job.object.content = msg;
|
|
316
|
-
}
|
|
317
|
-
} else {
|
|
318
|
-
job.object.content = msg;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (job.object.type === 'me') {
|
|
322
|
-
// message intended as command
|
|
323
|
-
// jsdoc does not like this octal escape sequence but it's needed for proper behavior in IRC
|
|
324
|
-
// so the following line needs to be commented out when the API doc is built.
|
|
325
|
-
// investigate:
|
|
326
|
-
// https://github.com/jsdoc2md/jsdoc-to-markdown/issues/197#issuecomment-976851915
|
|
327
|
-
const message = '\001ACTION ' + job.object.content + '\001';
|
|
328
|
-
client.raw('PRIVMSG ' + job.target.name + ' :' + message);
|
|
329
|
-
} else if (job.object.type === 'notice') {
|
|
330
|
-
// attempt to send as raw command
|
|
331
|
-
client.raw('NOTICE ' + job.target.name + ' :' + job.object.content);
|
|
332
|
-
} else if (this.__isJoined(job.target.name)) {
|
|
333
|
-
client.raw('PRIVMSG ' + job.target.name + ' :' + job.object.content);
|
|
334
|
-
} else {
|
|
335
|
-
return done("cannot send message to a channel of which you've not first joined.");
|
|
336
|
-
}
|
|
337
|
-
this.__jobQueue.push(done);
|
|
338
|
-
client.raw('PING ' + job.actor.name);
|
|
339
|
-
});
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Function: update
|
|
344
|
-
*
|
|
345
|
-
* Indicate a change (ie. room topic update, or nickname change).
|
|
346
|
-
*
|
|
347
|
-
* @param {object} job activity streams object // TODO LINK
|
|
348
|
-
* @param {object} done callback when job is done // TODO LINK
|
|
349
|
-
*
|
|
350
|
-
* @example change topic
|
|
351
|
-
*
|
|
352
|
-
* {
|
|
353
|
-
* context: 'irc',
|
|
354
|
-
* type: 'update',
|
|
355
|
-
* actor: {
|
|
356
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
357
|
-
* type: 'person',
|
|
358
|
-
* name: 'Nick Jennings',
|
|
359
|
-
* userName: 'slvrbckt'
|
|
360
|
-
* },
|
|
361
|
-
* target: {
|
|
362
|
-
* id: 'irc.freenode.net/a-room',
|
|
363
|
-
* type: 'room',
|
|
364
|
-
* name: '#a-room'
|
|
365
|
-
* },
|
|
366
|
-
* object: {
|
|
367
|
-
* type: 'topic',
|
|
368
|
-
* content: 'New version of Socekthub released!'
|
|
369
|
-
* }
|
|
370
|
-
* }
|
|
371
|
-
*
|
|
372
|
-
* @example change nickname
|
|
373
|
-
* {
|
|
374
|
-
* context: 'irc'
|
|
375
|
-
* type: 'udpate',
|
|
376
|
-
* actor: {
|
|
377
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
378
|
-
* type: 'person',
|
|
379
|
-
* name: 'slvrbckt'
|
|
380
|
-
* },
|
|
381
|
-
* object: {
|
|
382
|
-
* type: "address",
|
|
383
|
-
* },
|
|
384
|
-
* target: {
|
|
385
|
-
* id: 'cooldude@irc.freenode.net',
|
|
386
|
-
* type: 'person',
|
|
387
|
-
* name: cooldude
|
|
388
|
-
* }
|
|
389
|
-
* }
|
|
390
|
-
*/
|
|
391
|
-
IRC.prototype.update = function (job, credentials, done) {
|
|
392
|
-
this.debug('update() called for ' + job.actor.id);
|
|
393
|
-
this.__getClient(job.actor.id, false, (err, client) => {
|
|
394
|
-
if (err) { return done(err); }
|
|
395
|
-
if (job.object.type === 'address') {
|
|
396
|
-
this.debug('changing nick from ' + job.actor.name + ' to ' + job.target.name);
|
|
397
|
-
this.__handledActors.add(job.target.id);
|
|
398
|
-
this.__jobQueue.push((err) => {
|
|
399
|
-
if (err) {
|
|
400
|
-
this.__handledActors.delete(job.target.id);
|
|
401
|
-
return done(err);
|
|
402
|
-
}
|
|
403
|
-
credentials.object.nick = job.target.name;
|
|
404
|
-
credentials.actor.id = `${job.target.name}@${credentials.object.server}`;
|
|
405
|
-
credentials.actor.name = job.target.name;
|
|
406
|
-
this.updateActor(credentials);
|
|
407
|
-
done();
|
|
408
|
-
});
|
|
409
|
-
// send nick change command
|
|
410
|
-
client.raw(['NICK', job.target.name]);
|
|
411
|
-
} else if (job.object.type === 'topic') {
|
|
412
|
-
// update topic
|
|
413
|
-
this.debug('changing topic in channel ' + job.target.name);
|
|
414
|
-
this.__jobQueue.push(done);
|
|
415
|
-
client.raw(['topic', job.target.name, job.object.content]);
|
|
416
|
-
} else {
|
|
417
|
-
return done(`unknown update action.`);
|
|
418
|
-
}
|
|
419
|
-
client.raw('PING ' + job.actor.name);
|
|
420
|
-
});
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Function: query
|
|
425
|
-
*
|
|
426
|
-
* Indicate an intent to query something (e.g. get a list of users in a room).
|
|
427
|
-
*
|
|
428
|
-
* @param {object} job activity streams object // TODO LINK
|
|
429
|
-
* @param {object} done callback when job is done // TODO LINK
|
|
430
|
-
*
|
|
431
|
-
* @example
|
|
432
|
-
*
|
|
433
|
-
* {
|
|
434
|
-
* context: 'irc',
|
|
435
|
-
* type: 'query',
|
|
436
|
-
* actor: {
|
|
437
|
-
* id: 'slvrbckt@irc.freenode.net',
|
|
438
|
-
* type: 'person',
|
|
439
|
-
* name: 'Nick Jennings',
|
|
440
|
-
* userName: 'slvrbckt'
|
|
441
|
-
* },
|
|
442
|
-
* target: {
|
|
443
|
-
* id: 'irc.freenode.net/a-room',
|
|
444
|
-
* type: 'room',
|
|
445
|
-
* name: '#a-room'
|
|
446
|
-
* },
|
|
447
|
-
* object: {
|
|
448
|
-
* type: 'attendance'
|
|
449
|
-
* }
|
|
450
|
-
* }
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* // The above object might return:
|
|
454
|
-
* {
|
|
455
|
-
* context: 'irc',
|
|
456
|
-
* type: 'query',
|
|
457
|
-
* actor: {
|
|
458
|
-
* id: 'irc.freenode.net/a-room',
|
|
459
|
-
* type: 'room',
|
|
460
|
-
* name: '#a-room'
|
|
461
|
-
* },
|
|
462
|
-
* target: {},
|
|
463
|
-
* object: {
|
|
464
|
-
* type: 'attendance'
|
|
465
|
-
* members: [
|
|
466
|
-
* 'RyanGosling',
|
|
467
|
-
* 'PeeWeeHerman',
|
|
468
|
-
* 'Commando',
|
|
469
|
-
* 'Smoochie',
|
|
470
|
-
* 'neo'
|
|
471
|
-
* ]
|
|
472
|
-
* }
|
|
473
|
-
* }
|
|
474
|
-
*
|
|
475
|
-
*/
|
|
476
|
-
IRC.prototype.query = function (job, done) {
|
|
477
|
-
this.debug('query() called for ' + job.actor.id);
|
|
478
|
-
this.__getClient(job.actor.id, false, (err, client) => {
|
|
479
|
-
if (err) { return done(err); }
|
|
480
|
-
|
|
481
|
-
if (job.object.type === 'attendance') {
|
|
482
|
-
this.debug('query() - sending NAMES for ' + job.target.name);
|
|
483
|
-
client.raw(['NAMES', job.target.name]);
|
|
484
|
-
done();
|
|
485
|
-
} else {
|
|
486
|
-
done("unknown 'type' '" + job.object.type + "'");
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
IRC.prototype.cleanup = function (done) {
|
|
493
|
-
this.debug('cleanup() called');
|
|
494
|
-
this.__forceDisconnect = true;
|
|
495
|
-
if (typeof this.__client === 'object') {
|
|
496
|
-
if (typeof this.__client.end === 'function') {
|
|
497
|
-
this.__client.end();
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
this.initialized = false;
|
|
501
|
-
delete this.__client;
|
|
502
|
-
return done();
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
//
|
|
508
|
-
// Private methods
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
IRC.prototype.__isJoined = function (channel) {
|
|
513
|
-
if (channel.indexOf('#') === 0) {
|
|
514
|
-
// valid channel name
|
|
515
|
-
return this.__channels.has(channel);
|
|
516
|
-
} else {
|
|
517
|
-
// usernames are always OK to send to
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
IRC.prototype.__hasJoined = function (channel) {
|
|
524
|
-
this.debug('joined ' + channel);
|
|
525
|
-
// keep track of channels joined
|
|
526
|
-
if (! this.__channels.has(channel)) {
|
|
527
|
-
this.__channels.add(channel);
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
IRC.prototype.__hasLeft = function (channel) {
|
|
533
|
-
this.debug('left ' + channel);
|
|
534
|
-
// keep track of channels left
|
|
535
|
-
if (this.__channels.has(channel)) {
|
|
536
|
-
this.__channels.delete(channel);
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
IRC.prototype.__getClient = function (key, credentials, cb) {
|
|
542
|
-
this.debug('getClient called, connecting: ' + this.__clientConnecting);
|
|
543
|
-
if (this.__client) {
|
|
544
|
-
this.__handledActors.add(key);
|
|
545
|
-
return cb(null, this.__client);
|
|
546
|
-
} else if (this.__clientConnecting) {
|
|
547
|
-
// client is in the process of connecting, wait
|
|
548
|
-
setTimeout(function (_cb) {
|
|
549
|
-
if (this.__client) {
|
|
550
|
-
this.debug(`resolving delayed getClient call for ${key}`);
|
|
551
|
-
this.__handledActors.add(key);
|
|
552
|
-
return _cb(null, this.__client);
|
|
553
|
-
} else {
|
|
554
|
-
return cb('failed to get irc client, please try again.');
|
|
555
|
-
}
|
|
556
|
-
}.bind(this, cb), 30000);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (! credentials) {
|
|
561
|
-
return cb('no client found, and no credentials specified. you must connect first');
|
|
562
|
-
} else {
|
|
563
|
-
this.__connect(key, credentials, (err, client) => {
|
|
564
|
-
if (err) {
|
|
565
|
-
this.initialized = false;
|
|
566
|
-
return cb(err);
|
|
567
|
-
}
|
|
568
|
-
this.__handledActors.add(key);
|
|
569
|
-
this.__client = client;
|
|
570
|
-
this.__registerListeners(credentials.object.server);
|
|
571
|
-
this.initialized = true;
|
|
572
|
-
return cb(null, client);
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
IRC.prototype.__connect = function (key, credentials, cb) {
|
|
579
|
-
this.__clientConnecting = true;
|
|
580
|
-
const is_secure = (typeof credentials.object.secure === 'boolean') ?
|
|
581
|
-
credentials.object.secure : true;
|
|
582
|
-
|
|
583
|
-
const module_options = {
|
|
584
|
-
username: credentials.object.username || credentials.object.nick,
|
|
585
|
-
nicknames: [ credentials.object.nick ],
|
|
586
|
-
server: credentials.object.server || 'irc.libera.chat',
|
|
587
|
-
realname: credentials.actor.name || credentials.object.nick,
|
|
588
|
-
port: (credentials.object.port) ?
|
|
589
|
-
parseInt(credentials.object.port, 10) : (is_secure) ? 6697 : 6667,
|
|
590
|
-
debug: console.log
|
|
591
|
-
};
|
|
592
|
-
if (is_secure) {
|
|
593
|
-
module_options.connectOptions = { rejectUnauthorized: false };
|
|
594
|
-
}
|
|
595
|
-
if (credentials.object.sasl) {
|
|
596
|
-
module_options.saslPassword = credentials.object.password;
|
|
597
|
-
module_options.capabilities = { requires: [ "sasl" ] };
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
this.debug('attempting to connect to ' + module_options.server + ':' + module_options.port +
|
|
601
|
-
` transport: ${is_secure?'secure':'clear'} sasl: ${credentials.object.sasl}`);
|
|
602
|
-
|
|
603
|
-
const client = new IrcSocket(module_options, is_secure ? tls : net);
|
|
604
|
-
|
|
605
|
-
const __forceDisconnect = (err) => {
|
|
606
|
-
this.__forceDisconnect = true;
|
|
607
|
-
this.__clientConnecting = false;
|
|
608
|
-
if ((client) && (typeof client.end === 'function')) {
|
|
609
|
-
client.end();
|
|
610
|
-
}
|
|
611
|
-
if ((this.__client) && (typeof this.__client.end === 'function')) {
|
|
612
|
-
this.__client.end();
|
|
613
|
-
}
|
|
614
|
-
cb(err);
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
client.once('error', (err) => {
|
|
618
|
-
this.debug(`irc client 'error' occurred. `, err);
|
|
619
|
-
__forceDisconnect('error connecting to server.');
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
client.once('close', () => {
|
|
623
|
-
this.debug(`irc client 'close' event fired.`);
|
|
624
|
-
__forceDisconnect('connection to server closed.');
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
client.once('timeout', () => {
|
|
628
|
-
this.debug('timeout occurred, force-disconnect');
|
|
629
|
-
__forceDisconnect('connection timeout to server.');
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
client.connect().then((res) => {
|
|
633
|
-
if (res.isFail()) {
|
|
634
|
-
return cb(`unable to connect to server: ${res.fail()}`);
|
|
635
|
-
}
|
|
636
|
-
const capabilities = res.ok();
|
|
637
|
-
this.__clientConnecting = false;
|
|
638
|
-
if (this.__forceDisconnect) {
|
|
639
|
-
client.end();
|
|
640
|
-
return cb('force disconnect active, aborting connect.');
|
|
641
|
-
} else {
|
|
642
|
-
this.debug(`connected to ${module_options.server} capabilities: `, capabilities);
|
|
643
|
-
return cb(null, client);
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
IRC.prototype.__completeJob = function (err) {
|
|
650
|
-
this.debug(`completing job, queue count: ${this.__jobQueue.length}`);
|
|
651
|
-
const done = this.__jobQueue.shift();
|
|
652
|
-
if (typeof done === 'function') {
|
|
653
|
-
done(err);
|
|
654
|
-
} else {
|
|
655
|
-
this.debug('WARNING: job completion event received with an empty job queue.');
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
IRC.prototype.__registerListeners = function (server) {
|
|
661
|
-
this.irc2as = new IRC2AS({ server: server });
|
|
662
|
-
this.__client.on('data', (data) => {
|
|
663
|
-
this.irc2as.input(data);
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
this.irc2as.events.on('incoming', (asObject) => {
|
|
667
|
-
if ((typeof asObject.actor === 'object') &&
|
|
668
|
-
(typeof asObject.actor.name === 'string') &&
|
|
669
|
-
(this.__handledActors.has(asObject.actor.id))) {
|
|
670
|
-
this.__completeJob();
|
|
671
|
-
} else {
|
|
672
|
-
this.debug(
|
|
673
|
-
'calling sendToClient for ' + asObject.actor.id, [...this.__handledActors.keys()]);
|
|
674
|
-
this.sendToClient(asObject);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
this.irc2as.events.on('unprocessed', (string) => {
|
|
679
|
-
this.debug('unprocessed irc message:> ' + string);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// The generated eslint error expects that the `error` event is propagating an Error object
|
|
683
|
-
// however for irc2as this event delivers an AS object of type `error`.
|
|
684
|
-
// eslint-disable-next-line security-node/detect-unhandled-event-errors
|
|
685
|
-
this.irc2as.events.on('error', (asObject) => {
|
|
686
|
-
this.debug('message error response ' + asObject.object.content);
|
|
687
|
-
this.__completeJob(asObject.object.content);
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
this.irc2as.events.on('pong', (timestamp) => {
|
|
691
|
-
this.debug('received PONG at ' + timestamp);
|
|
692
|
-
this.__completeJob();
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
this.irc2as.events.on('ping', (timestamp) => {
|
|
696
|
-
this.debug('received PING at ' + timestamp);
|
|
697
|
-
this.__client.raw('PONG');
|
|
698
|
-
});
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
module.exports = IRC;
|