2016-10-09 19:14:02 +00:00
"use strict" ;
2014-09-13 21:29:45 +00:00
var _ = require ( "lodash" ) ;
2016-12-11 08:29:09 +00:00
var colors = require ( "colors/safe" ) ;
2016-10-09 19:15:20 +00:00
var pkg = require ( "../package.json" ) ;
2014-09-16 20:06:13 +00:00
var Chan = require ( "./models/chan" ) ;
2014-09-15 21:13:03 +00:00
var crypto = require ( "crypto" ) ;
2016-04-16 10:35:04 +00:00
var userLog = require ( "./userLog" ) ;
2014-09-13 21:29:45 +00:00
var Msg = require ( "./models/msg" ) ;
var Network = require ( "./models/network" ) ;
2016-03-07 21:09:42 +00:00
var ircFramework = require ( "irc-framework" ) ;
2014-10-01 15:59:35 +00:00
var Helper = require ( "./helper" ) ;
2014-09-13 21:29:45 +00:00
module . exports = Client ;
var id = 0 ;
var events = [
2016-03-08 18:50:48 +00:00
"connection" ,
2016-04-24 15:12:54 +00:00
"unhandled" ,
2014-09-13 21:29:45 +00:00
"ctcp" ,
"error" ,
2016-02-12 11:24:13 +00:00
"invite" ,
2014-09-13 21:29:45 +00:00
"join" ,
"kick" ,
"mode" ,
"motd" ,
"message" ,
"names" ,
"nick" ,
"part" ,
"quit" ,
"topic" ,
"welcome" ,
2016-03-09 20:04:07 +00:00
"list" ,
2014-09-13 21:29:45 +00:00
"whois"
] ;
var inputs = [
2016-03-27 15:57:59 +00:00
"ctcp" ,
2016-03-06 09:24:56 +00:00
"msg" ,
"part" ,
2014-09-13 21:29:45 +00:00
"action" ,
2016-11-19 08:24:39 +00:00
"away" ,
2014-09-13 21:29:45 +00:00
"connect" ,
2016-04-14 08:56:02 +00:00
"disconnect" ,
2014-09-13 21:29:45 +00:00
"invite" ,
"kick" ,
"mode" ,
2016-10-01 17:04:03 +00:00
"nick" ,
2014-09-13 21:29:45 +00:00
"notice" ,
2016-03-24 20:40:36 +00:00
"query" ,
2014-09-13 21:29:45 +00:00
"quit" ,
"raw" ,
"topic" ,
2016-05-29 02:07:34 +00:00
"list" ,
2016-10-27 05:59:36 +00:00
"whois"
2016-03-14 04:21:42 +00:00
] . reduce ( function ( plugins , name ) {
var path = "./plugins/inputs/" + name ;
var plugin = require ( path ) ;
2016-10-12 07:55:40 +00:00
plugin . commands . forEach ( command => plugins [ command ] = plugin ) ;
2016-03-14 04:21:42 +00:00
return plugins ;
} , { } ) ;
2014-09-13 21:29:45 +00:00
2016-02-17 00:14:43 +00:00
function Client ( manager , name , config ) {
2016-06-30 13:06:07 +00:00
if ( typeof config !== "object" ) {
config = { } ;
}
2014-09-13 21:29:45 +00:00
_ . merge ( this , {
2016-09-25 06:41:10 +00:00
lastActiveChannel : - 1 ,
attachedClients : { } ,
2014-09-13 21:29:45 +00:00
config : config ,
id : id ++ ,
2014-09-16 19:47:01 +00:00
name : name ,
2014-09-13 21:29:45 +00:00
networks : [ ] ,
2016-02-17 00:14:43 +00:00
sockets : manager . sockets ,
manager : manager
2014-09-13 21:29:45 +00:00
} ) ;
2016-05-31 21:28:31 +00:00
2014-09-15 21:13:03 +00:00
var client = this ;
2016-05-31 21:28:31 +00:00
2016-06-30 13:06:07 +00:00
if ( client . name && ! client . config . token ) {
client . updateToken ( function ( token ) {
client . manager . updateUser ( client . name , { token : token } ) ;
2014-09-13 21:29:45 +00:00
} ) ;
2016-06-30 13:06:07 +00:00
}
var delay = 0 ;
2016-10-12 07:55:40 +00:00
( client . config . networks || [ ] ) . forEach ( n => {
2016-06-30 13:06:07 +00:00
setTimeout ( function ( ) {
client . connect ( n ) ;
} , delay ) ;
delay += 1000 ;
} ) ;
2016-04-16 11:32:38 +00:00
2016-06-30 13:06:07 +00:00
if ( client . name ) {
2016-12-11 08:29:09 +00:00
log . info ( ` User ${ colors . bold ( client . name ) } loaded ` ) ;
2016-06-19 08:01:50 +00:00
}
2014-09-13 21:29:45 +00:00
}
Client . prototype . emit = function ( event , data ) {
if ( this . sockets !== null ) {
this . sockets . in ( this . id ) . emit ( event , data ) ;
}
2016-06-30 13:06:07 +00:00
if ( this . config . log === true ) {
2015-09-30 22:39:57 +00:00
if ( event === "msg" ) {
2014-09-16 19:47:01 +00:00
var target = this . find ( data . chan ) ;
if ( target ) {
2014-09-16 20:06:13 +00:00
var chan = target . chan . name ;
2015-09-30 22:39:57 +00:00
if ( target . chan . type === Chan . Type . LOBBY ) {
2014-09-16 20:06:13 +00:00
chan = target . network . host ;
}
2016-04-16 10:35:04 +00:00
userLog . write (
2014-09-16 20:06:13 +00:00
this . name ,
target . network . host ,
chan ,
data . msg
) ;
2014-09-16 19:47:01 +00:00
}
}
}
2014-09-13 17:18:42 +00:00
} ;
2014-09-13 21:29:45 +00:00
2016-10-09 08:54:44 +00:00
Client . prototype . find = function ( channelId ) {
2014-09-13 21:29:45 +00:00
var network = null ;
var chan = null ;
for ( var i in this . networks ) {
var n = this . networks [ i ] ;
2016-10-09 08:54:44 +00:00
chan = _ . find ( n . channels , { id : channelId } ) ;
2014-09-13 21:29:45 +00:00
if ( chan ) {
network = n ;
break ;
}
}
if ( network && chan ) {
return {
network : network ,
chan : chan
} ;
}
2016-10-09 08:54:44 +00:00
return false ;
2014-09-13 21:29:45 +00:00
} ;
Client . prototype . connect = function ( args ) {
2016-06-08 09:26:24 +00:00
var config = Helper . config ;
2014-09-13 21:29:45 +00:00
var client = this ;
2016-02-21 12:02:35 +00:00
2016-03-07 21:09:42 +00:00
var nick = args . nick || "lounge-user" ;
2016-04-03 05:12:49 +00:00
var webirc = null ;
2016-06-17 10:46:15 +00:00
var channels = [ ] ;
2016-06-19 17:12:42 +00:00
if ( args . channels ) {
var badName = false ;
2016-10-12 07:55:40 +00:00
args . channels . forEach ( chan => {
2016-06-19 17:12:42 +00:00
if ( ! chan . name ) {
badName = true ;
return ;
}
2016-06-17 10:46:15 +00:00
channels . push ( new Chan ( {
2016-06-19 17:12:42 +00:00
name : chan . name
2016-06-17 10:46:15 +00:00
} ) ) ;
} ) ;
2016-06-19 17:12:42 +00:00
if ( badName && client . name ) {
log . warn ( "User '" + client . name + "' on network '" + args . name + "' has an invalid channel which has been ignored" ) ;
}
// `join` is kept for backwards compatibility when updating from versions <2.0
// also used by the "connect" window
} else if ( args . join ) {
channels = args . join
2016-10-09 08:54:44 +00:00
. replace ( /,/g , " " )
2016-06-19 17:12:42 +00:00
. split ( /\s+/g )
. map ( function ( chan ) {
return new Chan ( {
name : chan
} ) ;
} ) ;
2016-06-17 10:46:15 +00:00
}
2016-03-07 21:09:42 +00:00
2016-11-19 20:23:51 +00:00
args . ip = args . ip || ( client . config && client . config . ip ) || client . ip ;
args . hostname = args . hostname || ( client . config && client . config . hostname ) || client . hostname ;
2016-03-07 21:09:42 +00:00
var network = new Network ( {
name : args . name || "" ,
host : args . host || "" ,
port : parseInt ( args . port , 10 ) || ( args . tls ? 6697 : 6667 ) ,
tls : ! ! args . tls ,
password : args . password ,
username : args . username || nick . replace ( /[^a-zA-Z0-9]/g , "" ) ,
realname : args . realname || "The Lounge User" ,
2016-04-03 05:12:49 +00:00
commands : args . commands ,
ip : args . ip ,
hostname : args . hostname ,
2016-06-17 10:46:15 +00:00
channels : channels ,
2016-03-07 21:09:42 +00:00
} ) ;
2016-05-12 11:15:38 +00:00
network . setNick ( nick ) ;
2016-03-07 21:09:42 +00:00
client . networks . push ( network ) ;
client . emit ( "network" , {
2016-07-02 08:26:23 +00:00
networks : [ network ]
2016-03-07 21:09:42 +00:00
} ) ;
2016-02-21 12:02:35 +00:00
if ( config . lockNetwork ) {
// This check is needed to prevent invalid user configurations
if ( args . host && args . host . length > 0 && args . host !== config . defaults . host ) {
2016-04-19 10:20:18 +00:00
network . channels [ 0 ] . pushMessage ( client , new Msg ( {
type : Msg . Type . ERROR ,
text : "Hostname you specified is not allowed."
2016-09-25 06:41:10 +00:00
} ) , true ) ;
2016-02-21 12:02:35 +00:00
return ;
}
2016-03-07 21:09:42 +00:00
network . host = config . defaults . host ;
network . port = config . defaults . port ;
network . tls = config . defaults . tls ;
2016-02-21 12:02:35 +00:00
}
2016-03-07 21:09:42 +00:00
if ( network . host . length === 0 ) {
2016-04-19 10:20:18 +00:00
network . channels [ 0 ] . pushMessage ( client , new Msg ( {
type : Msg . Type . ERROR ,
text : "You must specify a hostname to connect."
2016-09-25 06:41:10 +00:00
} ) , true ) ;
2016-02-21 12:02:35 +00:00
return ;
}
2016-05-02 05:11:57 +00:00
if ( config . webirc && network . host in config . webirc ) {
2016-11-19 20:23:51 +00:00
if ( ! args . hostname ) {
args . hostname = args . ip ;
}
2016-04-03 05:12:49 +00:00
if ( args . ip ) {
if ( config . webirc [ network . host ] instanceof Function ) {
webirc = config . webirc [ network . host ] ( client , args ) ;
} else {
webirc = {
password : config . webirc [ network . host ] ,
2016-10-09 19:15:20 +00:00
username : pkg . name ,
2016-04-03 05:12:49 +00:00
address : args . ip ,
hostname : args . hostname
} ;
}
} else {
log . warn ( "Cannot find a valid WEBIRC configuration for " + nick
+ "!" + network . username + "@" + network . host ) ;
}
}
2017-02-02 20:52:37 +00:00
network . irc = new ircFramework . Client ( {
2016-09-05 12:37:27 +00:00
version : pkg . name + " " + Helper . getVersion ( ) + " -- " + pkg . homepage ,
2016-03-07 21:09:42 +00:00
host : network . host ,
port : network . port ,
nick : nick ,
2016-11-19 20:23:51 +00:00
username : config . useHexIp ? Helper . ip2hex ( args . ip ) : network . username ,
2016-03-07 21:09:42 +00:00
gecos : network . realname ,
password : network . password ,
tls : network . tls ,
localAddress : config . bind ,
rejectUnauthorized : false ,
2017-02-02 20:52:37 +00:00
enable _echomessage : true ,
2016-04-13 07:10:44 +00:00
auto _reconnect : true ,
2016-07-02 18:45:41 +00:00
auto _reconnect _wait : 10000 + Math . floor ( Math . random ( ) * 1000 ) , // If multiple users are connected to the same network, randomize their reconnections a little
auto _reconnect _max _retries : 360 , // At least one hour (plus timeouts) worth of reconnections
2017-02-24 20:13:50 +00:00
ping _interval : 0 , // Disable client ping timeouts due to buggy implementation
2016-04-03 05:12:49 +00:00
webirc : webirc ,
2014-09-13 21:29:45 +00:00
} ) ;
2016-11-19 20:23:51 +00:00
2017-02-02 20:52:37 +00:00
network . irc . requestCap ( [
"znc.in/self-message" , // Legacy echo-message for ZNc
] ) ;
events . forEach ( plugin => {
var path = "./plugins/irc-events/" + plugin ;
require ( path ) . apply ( client , [
network . irc ,
network
] ) ;
} ) ;
network . irc . connect ( ) ;
2016-11-19 20:23:51 +00:00
client . save ( ) ;
2014-09-13 21:29:45 +00:00
} ;
2016-05-31 21:28:31 +00:00
Client . prototype . updateToken = function ( callback ) {
2016-02-17 00:14:43 +00:00
var client = this ;
2016-05-31 21:28:31 +00:00
crypto . randomBytes ( 48 , function ( err , buf ) {
2016-10-09 08:54:44 +00:00
if ( err ) {
throw err ;
}
2016-06-30 01:17:42 +00:00
callback ( client . config . token = buf . toString ( "hex" ) ) ;
2016-05-31 21:28:31 +00:00
} ) ;
} ;
Client . prototype . setPassword = function ( hash , callback ) {
var client = this ;
2016-06-30 01:17:42 +00:00
client . updateToken ( function ( token ) {
2016-05-31 21:28:31 +00:00
client . manager . updateUser ( client . name , {
2016-06-30 01:17:42 +00:00
token : token ,
2016-05-31 21:28:31 +00:00
password : hash
2016-11-19 20:54:16 +00:00
} , function ( err ) {
if ( err ) {
log . error ( "Failed to update password of" , client . name , err ) ;
return callback ( false ) ;
}
2016-05-31 21:28:31 +00:00
client . config . password = hash ;
2016-11-19 20:54:16 +00:00
return callback ( true ) ;
} ) ;
2016-05-31 21:28:31 +00:00
} ) ;
2016-02-17 00:14:43 +00:00
} ;
2014-09-13 21:29:45 +00:00
Client . prototype . input = function ( data ) {
var client = this ;
2016-10-12 07:55:40 +00:00
data . text . split ( "\n" ) . forEach ( line => {
2016-06-05 02:48:41 +00:00
data . text = line ;
client . inputLine ( data ) ;
} ) ;
} ;
Client . prototype . inputLine = function ( data ) {
var client = this ;
2016-05-11 01:19:09 +00:00
var text = data . text ;
2014-09-13 21:29:45 +00:00
var target = client . find ( data . target ) ;
2016-09-25 06:41:10 +00:00
if ( ! target ) {
return ;
}
// Sending a message to a channel is higher priority than merely opening one
// so that reloading the page will open this channel
this . lastActiveChannel = target . chan . id ;
2016-03-06 09:24:56 +00:00
// This is either a normal message or a command escaped with a leading '/'
if ( text . charAt ( 0 ) !== "/" || text . charAt ( 1 ) === "/" ) {
2017-03-11 18:09:37 +00:00
if ( target . chan . type === Chan . Type . LOBBY ) {
target . chan . pushMessage ( this , new Msg ( {
type : Msg . Type . ERROR ,
text : "Messages can not be sent to lobbies."
} ) ) ;
return ;
}
2016-03-06 09:24:56 +00:00
text = "say " + text . replace ( /^\// , "" ) ;
} else {
text = text . substr ( 1 ) ;
2014-09-13 21:29:45 +00:00
}
2016-03-06 09:24:56 +00:00
2014-09-13 21:29:45 +00:00
var args = text . split ( " " ) ;
2016-03-06 09:24:56 +00:00
var cmd = args . shift ( ) . toLowerCase ( ) ;
2016-04-03 09:58:59 +00:00
var irc = target . network . irc ;
var connected = irc && irc . connection && irc . connection . connected ;
2016-03-14 04:21:42 +00:00
if ( cmd in inputs ) {
2016-04-03 09:58:59 +00:00
var plugin = inputs [ cmd ] ;
if ( connected || plugin . allowDisconnected ) {
connected = true ;
plugin . input . apply ( client , [ target . network , target . chan , cmd , args ] ) ;
}
} else if ( connected ) {
irc . raw ( text ) ;
}
if ( ! connected ) {
2016-04-19 10:20:18 +00:00
target . chan . pushMessage ( this , new Msg ( {
type : Msg . Type . ERROR ,
text : "You are not connected to the IRC network, unable to send your command."
} ) ) ;
2016-03-06 09:24:56 +00:00
}
2014-09-13 21:29:45 +00:00
} ;
Client . prototype . more = function ( data ) {
var client = this ;
var target = client . find ( data . target ) ;
if ( ! target ) {
return ;
}
var chan = target . chan ;
var count = chan . messages . length - ( data . count || 0 ) ;
var messages = chan . messages . slice ( Math . max ( 0 , count - 100 ) , count ) ;
client . emit ( "more" , {
chan : chan . id ,
messages : messages
} ) ;
} ;
2016-12-21 10:38:50 +00:00
Client . prototype . open = function ( socketId , target ) {
// Opening a window like settings
if ( target === null ) {
this . attachedClients [ socketId ] = - 1 ;
return ;
}
target = this . find ( target ) ;
2016-09-25 06:41:10 +00:00
if ( ! target ) {
return ;
2014-09-21 16:46:43 +00:00
}
2016-09-25 06:41:10 +00:00
target . chan . firstUnread = 0 ;
target . chan . unread = 0 ;
target . chan . highlight = false ;
this . attachedClients [ socketId ] = target . chan . id ;
this . lastActiveChannel = target . chan . id ;
this . emit ( "open" , target . chan . id ) ;
2014-09-21 16:46:43 +00:00
} ;
2014-09-24 19:42:36 +00:00
Client . prototype . sort = function ( data ) {
var self = this ;
var type = data . type ;
var order = data . order || [ ] ;
2015-09-30 22:39:57 +00:00
var sorted = [ ] ;
2014-09-24 19:42:36 +00:00
switch ( type ) {
case "networks" :
2016-10-12 07:55:40 +00:00
order . forEach ( i => {
2014-09-24 19:42:36 +00:00
var find = _ . find ( self . networks , { id : i } ) ;
if ( find ) {
sorted . push ( find ) ;
}
} ) ;
self . networks = sorted ;
break ;
case "channels" :
var target = data . target ;
var network = _ . find ( self . networks , { id : target } ) ;
if ( ! network ) {
return ;
}
2016-10-12 07:55:40 +00:00
order . forEach ( i => {
2014-09-24 19:42:36 +00:00
var find = _ . find ( network . channels , { id : i } ) ;
if ( find ) {
sorted . push ( find ) ;
}
} ) ;
network . channels = sorted ;
break ;
}
2016-06-12 10:02:37 +00:00
self . save ( ) ;
2016-11-22 12:14:17 +00:00
// Sync order to connected clients
const syncOrder = sorted . map ( obj => obj . id ) ;
self . emit ( "sync_sort" , { order : syncOrder , type : type , target : data . target } ) ;
2014-09-24 19:42:36 +00:00
} ;
2016-02-17 02:29:44 +00:00
Client . prototype . names = function ( data ) {
var client = this ;
var target = client . find ( data . target ) ;
if ( ! target ) {
return ;
}
client . emit ( "names" , {
2016-03-23 10:15:44 +00:00
id : target . chan . id ,
2016-02-17 02:29:44 +00:00
users : target . chan . users
} ) ;
} ;
2014-09-13 21:29:45 +00:00
Client . prototype . quit = function ( ) {
2014-09-29 15:49:38 +00:00
var sockets = this . sockets . sockets ;
var room = sockets . adapter . rooms [ this . id ] || [ ] ;
for ( var user in room ) {
var socket = sockets . adapter . nsp . connected [ user ] ;
if ( socket ) {
socket . disconnect ( ) ;
}
}
2016-10-12 07:55:40 +00:00
this . networks . forEach ( network => {
2016-04-03 09:03:09 +00:00
if ( network . irc ) {
network . irc . quit ( "Page closed" ) ;
}
2014-09-13 21:29:45 +00:00
} ) ;
2014-09-13 17:18:42 +00:00
} ;
2014-10-11 20:44:56 +00:00
2016-09-25 06:41:10 +00:00
Client . prototype . clientAttach = function ( socketId ) {
2016-11-19 20:34:05 +00:00
var client = this ;
var save = false ;
client . attachedClients [ socketId ] = client . lastActiveChannel ;
// Update old networks to store ip and hostmask
client . networks . forEach ( network => {
if ( ! network . ip ) {
save = true ;
network . ip = ( client . config && client . config . ip ) || client . ip ;
}
if ( ! network . hostname ) {
var hostmask = ( client . config && client . config . hostname ) || client . hostname ;
if ( hostmask ) {
save = true ;
network . hostmask = hostmask ;
}
}
} ) ;
if ( save ) {
client . save ( ) ;
}
2016-09-25 06:41:10 +00:00
} ;
Client . prototype . clientDetach = function ( socketId ) {
delete this . attachedClients [ socketId ] ;
} ;
2016-11-19 21:00:54 +00:00
Client . prototype . save = _ . debounce ( function SaveClient ( ) {
2016-06-08 09:26:24 +00:00
if ( Helper . config . public ) {
2014-10-12 05:15:03 +00:00
return ;
}
2014-11-09 16:01:22 +00:00
2016-11-19 21:00:54 +00:00
const client = this ;
let json = { } ;
2016-10-12 08:00:04 +00:00
json . networks = this . networks . map ( n => n . export ( ) ) ;
2016-02-17 00:14:43 +00:00
client . manager . updateUser ( client . name , json ) ;
2016-11-19 21:00:54 +00:00
} , 1000 , { maxWait : 10000 } ) ;