mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-09-21 15:56:05 +08:00
autojoin and send buffered messages
This commit is contained in:
parent
0c341f4b92
commit
05deaab46e
|
@ -200,11 +200,13 @@ class IRCConnection extends EventEmitter {
|
|||
|
||||
let clear = () =>
|
||||
cursor.close(() => {
|
||||
if (this.dofetch) {
|
||||
return setImmediate(() => this.fetchMessages(true));
|
||||
} else {
|
||||
this.fetching = false;
|
||||
}
|
||||
db.redis.hset('irclast', this.session.auth.id.toString(), this.lastFetchedItem.toString(), () => {
|
||||
if (this.dofetch) {
|
||||
return setImmediate(() => this.fetchMessages(true));
|
||||
} else {
|
||||
this.fetching = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let processNext = () => {
|
||||
|
@ -231,7 +233,13 @@ class IRCConnection extends EventEmitter {
|
|||
return setImmediate(processNext);
|
||||
}
|
||||
|
||||
this.send({ source: message.nick, verb: 'PRIVMSG', target: message.channel ? message.channel.name : false, message: message.message });
|
||||
this.send({
|
||||
time: message.time,
|
||||
source: message.nick,
|
||||
verb: 'PRIVMSG',
|
||||
target: message.channel ? message.channel.name : false,
|
||||
message: message.message
|
||||
});
|
||||
setImmediate(processNext);
|
||||
});
|
||||
};
|
||||
|
@ -245,19 +253,75 @@ class IRCConnection extends EventEmitter {
|
|||
}
|
||||
|
||||
if (payload && typeof payload === 'object') {
|
||||
payload.source = payload.source || this.hostname;
|
||||
let message = [];
|
||||
|
||||
let message = [':' + payload.source];
|
||||
let verb = (payload.verb || '')
|
||||
.toString()
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
|
||||
if (payload.verb) {
|
||||
let cmd = (payload.verb || '')
|
||||
.toString()
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
if (codes.has(cmd)) {
|
||||
cmd = codes.get(cmd);
|
||||
let tags = payload.tags;
|
||||
if (tags && !Array.isArray(tags) && typeof tags === 'object') {
|
||||
tags = Object.keys(tags || {}).forEach(key => ({
|
||||
key,
|
||||
value: tags[key]
|
||||
}));
|
||||
}
|
||||
tags = [].concat(tags || []);
|
||||
|
||||
if (['PRIVMSG', 'NOTICE'].includes(verb.toUpperCase())) {
|
||||
let time = payload.time ? (typeof payload.time !== 'object' ? new Date(payload.time) : payload.time) : new Date();
|
||||
|
||||
if (this.capEnabled.has('server-time')) {
|
||||
tags.push({
|
||||
key: 'time',
|
||||
value: time.getISOString()
|
||||
});
|
||||
} else if (this.capEnabled.has('znc.in/server-time')) {
|
||||
tags.push({
|
||||
key: 't',
|
||||
value: Math.round(time.getTime() / 1000)
|
||||
});
|
||||
}
|
||||
message.push(cmd);
|
||||
}
|
||||
|
||||
if (tags.length) {
|
||||
let tagStr = tags
|
||||
.map(tag => {
|
||||
if (typeof tag.value === 'boolean') {
|
||||
if (tag.value === true) {
|
||||
return tag.key;
|
||||
}
|
||||
return;
|
||||
}
|
||||
return (
|
||||
tag.key +
|
||||
'=' +
|
||||
(tag.value || '').toString().replace(/[;\r\n\\ ]/g, c => {
|
||||
switch (c) {
|
||||
case ';':
|
||||
return '\\:';
|
||||
case '\r':
|
||||
return '\\r';
|
||||
case '\n':
|
||||
return '\\n';
|
||||
case ' ':
|
||||
return '\\s';
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.join(';');
|
||||
if (tagStr.length) {
|
||||
message.push('@' + tagStr);
|
||||
}
|
||||
}
|
||||
|
||||
payload.source = payload.source || this.hostname;
|
||||
message.push(':' + payload.source);
|
||||
|
||||
if (verb) {
|
||||
message.push(codes.has(verb) ? codes.get(verb) : verb);
|
||||
}
|
||||
|
||||
if (payload.target) {
|
||||
|
@ -424,14 +488,49 @@ class IRCConnection extends EventEmitter {
|
|||
return this.processQueue();
|
||||
}
|
||||
|
||||
let match = line.match(/^\s*(?::[^\s]+\s+)?([^\s]+)\s*/);
|
||||
let match = line.match(/^\s*(?:@([^\s]+)\s+)?(?::([^\s]+)\s+)?([^\s]+)\s*/);
|
||||
if (!match) {
|
||||
// TODO: send error message
|
||||
// Can it even happen?
|
||||
return this.processQueue();
|
||||
}
|
||||
|
||||
let verb = (match[1] || '').toString().toUpperCase();
|
||||
let tags = !match[1] ? false : new Map();
|
||||
(match[1] || '')
|
||||
.toString()
|
||||
.split(';')
|
||||
.forEach(elm => {
|
||||
if (!elm) {
|
||||
return;
|
||||
}
|
||||
let eqPos = elm.indexOf('=');
|
||||
if (eqPos < 0) {
|
||||
tags.set(elm, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let key = elm.substr(0, eqPos);
|
||||
let value = elm.substr(eqPos + 1).replace(/\\(.)/g, (m, c) => {
|
||||
switch (c) {
|
||||
case ':':
|
||||
return ';';
|
||||
case 's':
|
||||
return ' ';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 'n':
|
||||
return '\n';
|
||||
default:
|
||||
return c;
|
||||
}
|
||||
});
|
||||
tags.set(key, value);
|
||||
});
|
||||
|
||||
let prefix = (match[3] || '').toString() || false;
|
||||
let verb = (match[3] || '').toString().toUpperCase();
|
||||
let params = line.substr(match[0].length);
|
||||
let data;
|
||||
let separatorPos = params.indexOf(' :');
|
||||
|
@ -460,7 +559,7 @@ class IRCConnection extends EventEmitter {
|
|||
);
|
||||
|
||||
if (typeof this['command_' + verb] === 'function') {
|
||||
this['command_' + verb](params, () => {
|
||||
this['command_' + verb](tags, prefix, params, () => {
|
||||
this.processQueue();
|
||||
});
|
||||
} else {
|
||||
|
@ -476,6 +575,59 @@ class IRCConnection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
printNickList(channelData, fresh, next) {
|
||||
db.database
|
||||
.collection('nicks')
|
||||
.find({
|
||||
user: { $in: channelData.members }
|
||||
})
|
||||
.project({
|
||||
_id: true,
|
||||
nick: true,
|
||||
user: true
|
||||
})
|
||||
.toArray((err, nickList) => {
|
||||
if (err) {
|
||||
this.send({ verb: 'ERR_FILEERROR', params: channelData.channel, message: err.message });
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!nickList) {
|
||||
nickList = [];
|
||||
}
|
||||
|
||||
if (fresh) {
|
||||
nickList.unshift({
|
||||
nick: this.session.nick
|
||||
});
|
||||
}
|
||||
|
||||
let lines = [];
|
||||
let curLine = { members: [], length: 0 };
|
||||
nickList.forEach(nickData => {
|
||||
curLine.members.push(nickData.nick);
|
||||
curLine.length += nickData.nick.length + 1;
|
||||
if (curLine.length > 400) {
|
||||
lines.push(curLine);
|
||||
curLine = { members: [], length: 0 };
|
||||
}
|
||||
});
|
||||
if (curLine.length) {
|
||||
lines.push(curLine);
|
||||
}
|
||||
|
||||
this.send({ source: this.getFormattedName(), verb: 'JOIN', target: false, message: channelData.channel });
|
||||
|
||||
lines.forEach(line => {
|
||||
this.send({ verb: 'RPL_NAMREPLY', params: ['=', channelData.channel], message: line.members.join(' ') });
|
||||
});
|
||||
|
||||
this.send({ verb: 'RPL_ENDOFNAMES', params: channelData.channel, message: 'End of /NAMES list' });
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
getFormattedName(skipNick, nick) {
|
||||
nick = nick || this.session.nick;
|
||||
return (!skipNick && nick ? nick + '!' : '') + (this.session.user || 'unknown') + '@' + this.session.clientHostname;
|
||||
|
@ -530,12 +682,71 @@ class IRCConnection extends EventEmitter {
|
|||
verb: 'NOTICE',
|
||||
message: 'This server requires all users to be authenticated. Identify via /msg NickServ identify <password>'
|
||||
});
|
||||
} else {
|
||||
this.initializeSubscriptions();
|
||||
}
|
||||
|
||||
this.starting = false;
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
initializeSubscriptions(next) {
|
||||
next = next || (() => false);
|
||||
db.database
|
||||
.collection('channels')
|
||||
.find({
|
||||
members: this.session.auth.id
|
||||
})
|
||||
.project({
|
||||
_id: true,
|
||||
channel: true,
|
||||
members: true
|
||||
})
|
||||
.toArray((err, channels) => {
|
||||
if (err) {
|
||||
this.server.logger.error(
|
||||
{
|
||||
err,
|
||||
tnx: 'setup',
|
||||
cid: this.id,
|
||||
user: this.session.auth.id
|
||||
},
|
||||
'Failed loading channels. %s',
|
||||
err.message
|
||||
);
|
||||
return next();
|
||||
}
|
||||
if (Array.isArray(channels)) {
|
||||
channels.forEach(channelData => {
|
||||
this.server.logger.info(
|
||||
{
|
||||
tnx: 'setup',
|
||||
cid: this.id,
|
||||
channel: channelData._id
|
||||
},
|
||||
'Joining %s to channel %s',
|
||||
this.session.auth.id,
|
||||
channelData._id
|
||||
);
|
||||
|
||||
this.subscribe([this.session.ns, '#', channelData._id].join('.'));
|
||||
this.printNickList(channelData, false, next);
|
||||
//this.send({ source: this.getFormattedName(), verb: 'JOIN', target: false, message: channelData.channel });
|
||||
});
|
||||
}
|
||||
|
||||
// private messages
|
||||
this.subscribe([this.session.ns, '%', this.session.auth.id].join('.'));
|
||||
|
||||
// general notifications
|
||||
this.subscribe([this.session.ns, '!', '*'].join('.'));
|
||||
|
||||
this.fetchMessages();
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(user, password, next) {
|
||||
this.server.userHandler.authenticate(
|
||||
user.replace(/\+/, '@'),
|
||||
|
@ -575,60 +786,37 @@ class IRCConnection extends EventEmitter {
|
|||
}
|
||||
|
||||
let ns = userData.ns;
|
||||
|
||||
if (userData.address) {
|
||||
let parts = userData.address.split('@');
|
||||
this.session.user = parts.shift();
|
||||
this.session.clientHostname = parts.join('@');
|
||||
if (!ns) {
|
||||
ns = this.session.clientHostname;
|
||||
db.redis.hget('irclast', userData._id.toString(), (err, ircLast) => {
|
||||
if (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
this.session.nick = this.session.nick || userData.username;
|
||||
this.session.ns = ns || 'root';
|
||||
|
||||
db.database
|
||||
.collection('channels')
|
||||
.find({
|
||||
members: userData._id
|
||||
})
|
||||
.project({
|
||||
_id: true,
|
||||
channel: true
|
||||
})
|
||||
.toArray((err, channels) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
if (ircLast) {
|
||||
let lastFetchedItem = new ObjectID(ircLast);
|
||||
let maxAge = Math.round(Date.now() / 1000 - 2 * 24 * 3600);
|
||||
if (lastFetchedItem.getTimestamp().getTime() < maxAge * 1000) {
|
||||
lastFetchedItem = new ObjectID(maxAge);
|
||||
}
|
||||
if (Array.isArray(channels)) {
|
||||
channels.forEach(channelData => {
|
||||
this.server.logger.info(
|
||||
{
|
||||
tnx: 'setup',
|
||||
cid: this.id,
|
||||
channel: channelData._id
|
||||
},
|
||||
'Joining %s to channel %s',
|
||||
userData._id,
|
||||
channelData._id
|
||||
);
|
||||
this.subscribe([this.session.ns, '#', channelData._id].join('.'));
|
||||
this.send({ source: this.getFormattedName(), verb: 'JOIN', target: false, message: channelData.channel });
|
||||
});
|
||||
this.lastFetchedItem = lastFetchedItem;
|
||||
}
|
||||
|
||||
if (userData.address) {
|
||||
let parts = userData.address.split('@');
|
||||
this.session.user = parts.shift();
|
||||
this.session.clientHostname = parts.join('@');
|
||||
if (!ns) {
|
||||
ns = this.session.clientHostname;
|
||||
}
|
||||
}
|
||||
|
||||
// private messages
|
||||
this.subscribe([this.session.ns, '%', userData._id].join('.'));
|
||||
this.session.nick = this.session.nick || userData.username;
|
||||
this.session.ns = ns || 'root';
|
||||
|
||||
// general notifications
|
||||
this.subscribe([this.session.ns, '!', '*'].join('.'));
|
||||
|
||||
next(null, {
|
||||
id: userData._id,
|
||||
username: userData.username
|
||||
});
|
||||
next(null, {
|
||||
id: userData._id,
|
||||
username: userData.username
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -796,7 +984,7 @@ class IRCConnection extends EventEmitter {
|
|||
this.close();
|
||||
}
|
||||
|
||||
command_PING(params, next) {
|
||||
command_PING(tags, prefix, params, next) {
|
||||
if (!params.length) {
|
||||
this.send({ verb: 'ERR_NEEDMOREPARAMS', params: 'PING', message: 'Not enough parameters' });
|
||||
return next();
|
||||
|
@ -810,11 +998,11 @@ class IRCConnection extends EventEmitter {
|
|||
return next();
|
||||
}
|
||||
|
||||
command_PONG(params, next) {
|
||||
command_PONG(tags, prefix, params, next) {
|
||||
return next();
|
||||
}
|
||||
|
||||
command_NICK(params, next) {
|
||||
command_NICK(tags, prefix, params, next) {
|
||||
let currentSource = this.getFormattedName();
|
||||
|
||||
if (params.length > 1) {
|
||||
|
@ -839,7 +1027,7 @@ class IRCConnection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
command_PASS(params, next) {
|
||||
command_PASS(tags, prefix, params, next) {
|
||||
if (!params.length) {
|
||||
this.send({ verb: 'ERR_NEEDMOREPARAMS', params: 'PASS', message: 'Not enough parameters' });
|
||||
return next();
|
||||
|
@ -852,7 +1040,7 @@ class IRCConnection extends EventEmitter {
|
|||
return next();
|
||||
}
|
||||
|
||||
command_USER(params, next) {
|
||||
command_USER(tags, prefix, params, next) {
|
||||
if (this.session.user) {
|
||||
this.send({ verb: 'ERR_ALREADYREGISTERED', message: 'You may not reregister' });
|
||||
return next();
|
||||
|
@ -884,7 +1072,7 @@ class IRCConnection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
command_JOIN(params, next) {
|
||||
command_JOIN(tags, prefix, params, next) {
|
||||
if (!this.session.user || !this.session.nick) {
|
||||
this.send({ verb: 'ERR_NOTREGISTERED', params: 'JOIN', message: 'You have not registered' });
|
||||
return next();
|
||||
|
@ -925,57 +1113,7 @@ class IRCConnection extends EventEmitter {
|
|||
|
||||
// notify other instances of self
|
||||
this.publish([this.session.ns, '%', idString].join('.'), eventData);
|
||||
|
||||
db.database
|
||||
.collection('nicks')
|
||||
.find({
|
||||
user: { $in: channelData.members }
|
||||
})
|
||||
.project({
|
||||
_id: true,
|
||||
nick: true,
|
||||
user: true
|
||||
})
|
||||
.toArray((err, nickList) => {
|
||||
if (err) {
|
||||
this.send({ verb: 'ERR_FILEERROR', params: channelData.channel, message: err.message });
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!nickList) {
|
||||
nickList = [];
|
||||
}
|
||||
|
||||
if (fresh) {
|
||||
nickList.unshift({
|
||||
nick: this.session.nick
|
||||
});
|
||||
}
|
||||
|
||||
let lines = [];
|
||||
let curLine = { members: [], length: 0 };
|
||||
nickList.forEach(nickData => {
|
||||
curLine.members.push(nickData.nick);
|
||||
curLine.length += nickData.nick.length + 1;
|
||||
if (curLine.length > 400) {
|
||||
lines.push(curLine);
|
||||
curLine = { members: [], length: 0 };
|
||||
}
|
||||
});
|
||||
if (curLine.length) {
|
||||
lines.push(curLine);
|
||||
}
|
||||
|
||||
this.send({ source: this.getFormattedName(), verb: 'JOIN', target: false, message: channelData.channel });
|
||||
|
||||
lines.forEach(line => {
|
||||
this.send({ verb: 'RPL_NAMREPLY', params: ['=', channelData.channel], message: line.members.join(' ') });
|
||||
});
|
||||
|
||||
this.send({ verb: 'RPL_ENDOFNAMES', params: channelData.channel, message: 'End of /NAMES list' });
|
||||
|
||||
next();
|
||||
});
|
||||
this.printNickList(channelData, fresh, next);
|
||||
};
|
||||
|
||||
let tryCount = 0;
|
||||
|
@ -1056,7 +1194,7 @@ class IRCConnection extends EventEmitter {
|
|||
tryGetChannel();
|
||||
}
|
||||
|
||||
command_PART(params, next) {
|
||||
command_PART(tags, prefix, params, next) {
|
||||
if (!this.session.user || !this.session.nick) {
|
||||
this.send({ verb: 'ERR_NOTREGISTERED', params: 'JOIN', message: 'You have not registered' });
|
||||
return next();
|
||||
|
@ -1119,7 +1257,7 @@ class IRCConnection extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
command_PRIVMSG(params, next) {
|
||||
command_PRIVMSG(tags, prefix, params, next) {
|
||||
if (!this.session.user || !this.session.nick) {
|
||||
this.send({ verb: 'ERR_NOTREGISTERED', params: 'PRIVMSG', message: 'You have not registered' });
|
||||
return next();
|
||||
|
@ -1138,6 +1276,8 @@ class IRCConnection extends EventEmitter {
|
|||
|
||||
if (target.trim().toLowerCase() === 'nickserv') {
|
||||
return this.command_NICKSERV(
|
||||
tags,
|
||||
prefix,
|
||||
params
|
||||
.slice(1)
|
||||
.join(' ')
|
||||
|
@ -1251,13 +1391,13 @@ class IRCConnection extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
command_CAP(params, next) {
|
||||
command_CAP(tags, prefix, params, next) {
|
||||
if (!params.length) {
|
||||
this.send({ verb: 'ERR_NEEDMOREPARAMS', target: this.session.nick || '*', params: 'CAP', message: 'Not enough parameters' });
|
||||
return next();
|
||||
}
|
||||
|
||||
let allowed = ['sasl'];
|
||||
let allowed = ['sasl', 'server-time', 'znc.in/server-time'];
|
||||
|
||||
this.capStarted = true;
|
||||
let subcommand = params
|
||||
|
@ -1323,7 +1463,7 @@ class IRCConnection extends EventEmitter {
|
|||
next();
|
||||
}
|
||||
|
||||
command_NICKSERV(params, next) {
|
||||
command_NICKSERV(tags, prefix, params, next) {
|
||||
if (!params.length) {
|
||||
this.send({ verb: 'ERR_NEEDMOREPARAMS', target: this.session.nick || '*', params: 'CAP', message: 'Not enough parameters' });
|
||||
return next();
|
||||
|
@ -1360,7 +1500,7 @@ class IRCConnection extends EventEmitter {
|
|||
target: this.session.nick,
|
||||
message: 'You are now identified for ' + this.session.user
|
||||
});
|
||||
return this.verifyNickChange(false, next);
|
||||
return this.verifyNickChange(false, () => this.initializeSubscriptions(next));
|
||||
} else {
|
||||
this.send({
|
||||
source: 'NickServ!NickServ@services.',
|
||||
|
@ -1376,7 +1516,7 @@ class IRCConnection extends EventEmitter {
|
|||
next();
|
||||
}
|
||||
|
||||
command_AUTHENTICATE(params, next) {
|
||||
command_AUTHENTICATE(tags, prefix, params, next) {
|
||||
if (!params.length) {
|
||||
this.send({ verb: 'ERR_NEEDMOREPARAMS', target: this.session.nick || '*', params: 'AUTHENTICATE', message: 'Not enough parameters' });
|
||||
return next();
|
||||
|
|
Loading…
Reference in a new issue