Skip to content

Commit 06fb43c

Browse files
author
Stig Otnes Kolstad
committedJan 22, 2018
New approach
1 parent b9a3aac commit 06fb43c

File tree

3 files changed

+168
-24
lines changed

3 files changed

+168
-24
lines changed
 

‎event-stream.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const { Transform } = require('stream')
2+
const EventEmitter = require('events')
3+
4+
function EventStream (patternMap) {
5+
return new Transform({
6+
transform(chunk, encoding, done) {
7+
const str = chunk.toString('utf8')
8+
for ([pattern, fn] of patternMap) {
9+
const result = pattern.exec(str)
10+
if (result) {
11+
fn(result)
12+
// Stop looking after first match
13+
break;
14+
}
15+
}
16+
done()
17+
}
18+
})
19+
}
20+
21+
module.exports = EventStream
22+

‎irc.js

+79-24
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,88 @@
1-
const Emitter = require('events')
2-
const Transform = require('stream').Transform
3-
4-
const patternMatch = (matchers) => {
5-
return es.map((data, cb) => {
6-
for ([pattern, fn] of matchers) {
7-
const result = pattern.exec(data)
8-
if (result) {
9-
fn(result)
10-
return
11-
}
1+
const LineBuffer = require('./line-buffer')
2+
const EventStream = require('./event-stream')
3+
const { Socket, createServer } = require('net')
4+
5+
class Client extends Socket {
6+
constructor (options) {
7+
super(options)
8+
this.setEncoding('utf8')
9+
10+
if (options.dryRun) {
11+
const serv = createServer().listen(11337)
12+
serv.on('connection', s => s.on('data', (data) => console.log('SERVER', data)))
13+
Object.assign(options, {host: 'localhost', port: 11337})
1214
}
13-
})
14-
}
15+
this.connect(options)
16+
17+
this.on('connect', () => this._identify(options.nick))
18+
this.on('ping', (host) => this.send(`PONG ${host}`))
19+
20+
this
21+
.pipe(new LineBuffer())
22+
.pipe(EventStream([
23+
[ /PING (\S+)/, ([, hostname]) => this.emit('ping', {hostname}) ],
24+
[ /^(\S+) PRIVMSG (\S+) :(.+)/, ([, from, to, msg]) => this.emit('msg', {from, to, msg}) ],
25+
[ /^\S+ 376/, () => this.emit('ready') ], // End of MOTD
26+
[ /^\S+ 433/, () => this.emit('error', 'Nickname in use') ],
27+
[ /^\S+ 451/, () => this.emit('error', 'Not registered') ],
28+
]))
29+
}
1530

16-
const emitter = new Emitter()
17-
const emit = emitter.emit
31+
send (msg, cb) {
32+
super.write(`${msg}\r\n`, cb)
33+
console.log('***', msg)
34+
}
1835

19-
const matchers = [
20-
[ /PING (\S+)/, ([, hostname]) => emit('ping', {hostname}) ],
21-
[ /^(\S+) PRIVMSG (\S+) !(.+))/, (from, to, msg) => emit('msg', {from, to, msg}) ],
22-
]
36+
msg (to, text) {
37+
this.send(`PRIVMSG ${to} :${text}`)
38+
}
2339

24-
module.exports = emitter
40+
join (chan) {
41+
this.send(`JOIN ${chan}`)
42+
}
43+
44+
nick (nick) {
45+
this.send(`NICK ${nick}`)
46+
}
47+
48+
quit (msg) {
49+
this.send(`QUIT :${msg}`)
50+
}
51+
52+
_identify (nick) {
53+
this.send(`USER ${nick} * * :${nick}`)
54+
this.nick(nick)
55+
}
56+
}
2557

2658
if (!module.parent) {
27-
//socket
59+
const client = new Client({
60+
host: 'chat.freenode.net',
61+
port: 6667,
62+
nick: 'sshowfojs',
63+
dryRun: true
64+
})
65+
const chan = '#hackeriet'
66+
67+
client.pipe(process.stdout)
68+
2869
process.stdin
29-
.pipe(es.split())
30-
.pipe(patternMatch(matchers))
31-
.pipe(process.stdout)
70+
.pipe(new LineBuffer())
71+
.on('data', (line) => client.send(`PRIVMSG ${chan} :${line}`))
72+
73+
client.on('ready', () => client.join(chan))
74+
client.on('msg', (msg) => console.log(msg))
75+
client.on('end', () => process.exit(0))
76+
77+
let quitAttempts = 0
78+
process.on('SIGINT', () => {
79+
if (++quitAttempts > 1) {
80+
process.exit(1)
81+
}
82+
83+
client.send('QUIT :quitting', () => {
84+
console.log('Quit successfully')
85+
})
86+
})
3287
}
3388

‎line-buffer.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
3+
This module splits an incoming text stream by newline \n, and buffers
4+
text without a newline until an additional newline comes along, or on
5+
before the stream is closed.
6+
7+
*/
8+
9+
const Emitter = require('events')
10+
const { Transform } = require('stream')
11+
12+
const EOL = '\n'
13+
14+
class LineBuffer extends Transform {
15+
constructor () {
16+
super({
17+
// Split streaming text into single line chunks
18+
transform: (chunk, enc, done) => {
19+
const str = this.buf + chunk.toString()
20+
this.buf = ''
21+
const parts = str.split(/\r?\n/)
22+
23+
// Emit lines one by one
24+
while (parts.length > 1) {
25+
const line = parts.shift()
26+
this.push(line + EOL)
27+
}
28+
29+
// Buffer text that doesn't have an EOL.
30+
// The first item of the array may be empty, which is okay.
31+
this.buf += parts[0]
32+
done(null)
33+
},
34+
35+
// Empty the buffer before closing stream
36+
flush: (done) => {
37+
this.push(this.buf + EOL)
38+
this.buf = ''
39+
done(null)
40+
}
41+
})
42+
43+
// Initialise an empty buffer
44+
this.buf = ''
45+
46+
// TODO: Move to a test instead. Doesn't need to be here.
47+
this.on('end', () => {
48+
if (this.buf.length) {
49+
console.error('Stream ended with text still in buffer')
50+
}
51+
})
52+
}
53+
}
54+
55+
module.exports = LineBuffer
56+
57+
// TEST
58+
if (!module.parent) {
59+
60+
const lb = new LineBuffer()
61+
lb.pipe(process.stdout)
62+
63+
lb.write('first line\nsecond line\nand some text with no newline')
64+
lb.write(', but here the newline comes\n')
65+
lb.end('last and end\n\n')
66+
}
67+

0 commit comments

Comments
 (0)
Please sign in to comment.