const net = require("net") const User = require("../src/user.js") const Channel = require("../src/channel.js") const RPL_WELCOME = '001' const ERR_NOSUCHNICK = '401' const ERR_NOSUCHCHANNEL = '403' const ERR_CANNOTSENDTOCHAN = '404' const ERR_UNKNOWNCOMMAND = '421' const ERR_ERRONEUSNICKNAME = '432' const ERR_NONICKNAMEGIVEN = '431' const ERR_NICKNAMEINUSE = '433' const ERR_NEEDMOREPARAMS = '461' let server = {} server.create = function create(config = {}) { let password if (config.password) { password = config.password } let userlist = {} let channellist = {} let server = net.createServer((socket) => { const user = new User(socket, !config.password) socket.on('data', function (data) { data.toString("ascii").split("\r\n").forEach(function (commandline) { if (commandline == "") { return } let splitted = commandline.split(" ") let tokenized = [] let lastParam = -1 for (let i = 0; i < splitted.length; i++) { if (lastParam > 0) { tokenized[lastParam] = tokenized[lastParam] + " " + splitted[i] } else if (splitted[i].charAt(0) == ":" && i > 0) { lastParam = i tokenized[lastParam] = splitted[i].slice(1) } else { tokenized[i] = splitted[i] } } let command = tokenized[0].toUpperCase() switch (command) { case "PING": if (tokenized[1] && tokenized[1] === "irc.example.com") { socket.write("PONG irc.example.com\r\n") } break; case "NICK": if (tokenized[1]) { let nickname = tokenized[1] // nick collision test if (!Object.keys(userlist).includes(nickname)) { user.setNickname(nickname) userlist[user.nickname] = user } else { socket.write(`:irc.example.com ${ERR_NICKNAMEINUSE} ${nickname} :Nickname is already in use`, 'ascii') } } else { socket.write(`:irc.example.com ${ERR_NONICKNAMEGIVEN} :No nickname given`, 'ascii') } break; case "USER": // ignoring servername and hostname, not useful these days user.register(tokenized[1], tokenized[4]) let address = user.getAddress() socket.write(`:irc.example.com 001 ${user.nickname} :Welcome to the example IRC Project ${user.nickname}!~${user.username}@${address}\r\n`, "ascii") break; case "PRIVMSG": let target let message = tokenized[2] if (typeof tokenized[1] === 'string' && tokenized[1].charAt(0) === "#") { target = channellist[tokenized[1]] } else { let targetUser = userlist[tokenized[1]] if (targetUser && targetUser.registered) { target = targetUser } } if (target) { target.sendMsg(user, message) } else { user.sendRaw(`:irc.example.com ${ERR_NOSUCHNICK} ${target} no such nick/channel`) } break; case "JOIN": if (!(tokenized[1].charAt(0) === "#")) { socket.write(`:irc.example.com ${ERR_CANNOTSENDTOCHAN}: invalid channel name`) break; } let channelname = tokenized[1] if (Object.keys(channellist).includes(channelname)) { channellist[channelname].join(user) } else { let channel = new Channel(channelname) channel.join(user) channellist[channelname] = channel } break; case "QUIT": server.closeConnection(user.nickname) break; case "PASS": if (tokenized[1] === config.password) { user.authenticated = true } else { user.closeConnection() } break; default: console.error(`Unknown command: ${command}`); } }); }).on('error', (err) => { console.error(err); }) }).on('error', (err) => { console.error(err); throw err; }); server.getUserlist = function () { return userlist } server.closeConnection = function (user_nickname) { let user = userlist[user_nickname] user.closeConnection() delete userlist[user_nickname]; } return server } module.exports = server