const themes = require('./theme.js'); const stylize = require('./colorparser.js'); window.irc = { socketUp: false, primaryFrame: null, config: { colors: true, sharedInput: false, timestamps: true, timestampFormat: 'HH:mm:ss', colorNicknames: true, colorNicklist: false, scrollOnResize: true, scrollOnFocus: true, theme: 'default' }, serverData: {}, serverChatQueue: {}, chatType: 'simple', documentTitle: 'TeemantIRC', }; const clientdom = {connector: {}, settings: {}}; const colorizer = { get_random_color: function(nickname) { let themefunc = themes.available[irc.config.theme].nick_pallete; Math.seedrandom(nickname); let h = rand(themefunc.H[0], themefunc.H[1]); let s = rand(themefunc.S[0], themefunc.S[1]); let l = rand(themefunc.L[0], themefunc.L[1]); return 'hsl(' + h + ',' + s + '%,' + l + '%)'; }, strip: function(message) { return message.replace(/(\x03\d{0,2}(,\d{0,2})?)/g, '').replace(/[\x0F\x02\x16\x1F]/g, ''); } }; let urlParams = {}; /*********************\ |** **| |** UTILITIES **| |** **| \*********************/ const validators = {}; validators.iporhost = function(str) { let valid = false; if(str.match(/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/i)) { valid = true; } else if (str.match(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i)) { valid = true; } return valid; }; validators.nickname = function(str) { if(str.match(/[a-z_\-\[\]\\^{}|`][a-z0-9_\-\[\]\\^{}|`]*/i)) { return true; } return false; }; validators.escapeHTML = function(str) { return str.replace(/\/, '>'); }; Date.prototype.format = function (format, utc){ var date = this; var MMMM = ['\x00', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; var MMM = ['\x01', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var dddd = ['\x02', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; var ddd = ['\x03', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; function ii(i, len) { var s = i + ''; len = len || 2; while (s.length < len) s = '0' + s; return s; } var y = utc ? date.getUTCFullYear() : date.getFullYear(); format = format.replace(/(^|[^\\])yyyy+/g, '$1' + y); format = format.replace(/(^|[^\\])yy/g, '$1' + y.toString().substr(2, 2)); format = format.replace(/(^|[^\\])y/g, '$1' + y); var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1; format = format.replace(/(^|[^\\])MMMM+/g, '$1' + MMMM[0]); format = format.replace(/(^|[^\\])MMM/g, '$1' + MMM[0]); format = format.replace(/(^|[^\\])MM/g, '$1' + ii(M)); format = format.replace(/(^|[^\\])M/g, '$1' + M); var d = utc ? date.getUTCDate() : date.getDate(); format = format.replace(/(^|[^\\])dddd+/g, '$1' + dddd[0]); format = format.replace(/(^|[^\\])ddd/g, '$1' + ddd[0]); format = format.replace(/(^|[^\\])dd/g, '$1' + ii(d)); format = format.replace(/(^|[^\\])d/g, '$1' + d); var H = utc ? date.getUTCHours() : date.getHours(); format = format.replace(/(^|[^\\])HH+/g, '$1' + ii(H)); format = format.replace(/(^|[^\\])H/g, '$1' + H); var h = H > 12 ? H - 12 : H == 0 ? 12 : H; format = format.replace(/(^|[^\\])hh+/g, '$1' + ii(h)); format = format.replace(/(^|[^\\])h/g, '$1' + h); var m = utc ? date.getUTCMinutes() : date.getMinutes(); format = format.replace(/(^|[^\\])mm+/g, '$1' + ii(m)); format = format.replace(/(^|[^\\])m/g, '$1' + m); var s = utc ? date.getUTCSeconds() : date.getSeconds(); format = format.replace(/(^|[^\\])ss+/g, '$1' + ii(s)); format = format.replace(/(^|[^\\])s/g, '$1' + s); var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); format = format.replace(/(^|[^\\])fff+/g, '$1' + ii(f, 3)); f = Math.round(f / 10); format = format.replace(/(^|[^\\])ff/g, '$1' + ii(f)); f = Math.round(f / 10); format = format.replace(/(^|[^\\])f/g, '$1' + f); var T = H < 12 ? 'AM' : 'PM'; format = format.replace(/(^|[^\\])TT+/g, '$1' + T); format = format.replace(/(^|[^\\])T/g, '$1' + T.charAt(0)); var t = T.toLowerCase(); format = format.replace(/(^|[^\\])tt+/g, '$1' + t); format = format.replace(/(^|[^\\])t/g, '$1' + t.charAt(0)); var tz = -date.getTimezoneOffset(); var K = utc || !tz ? 'Z' : tz > 0 ? '+' : '-'; if (!utc) { tz = Math.abs(tz); var tzHrs = Math.floor(tz / 60); var tzMin = tz % 60; K += ii(tzHrs) + ':' + ii(tzMin); } format = format.replace(/(^|[^\\])K/g, '$1' + K); var day = (utc ? date.getUTCDay() : date.getDay()) + 1; format = format.replace(new RegExp(dddd[0], 'g'), dddd[day]); format = format.replace(new RegExp(ddd[0], 'g'), ddd[day]); format = format.replace(new RegExp(MMMM[0], 'g'), MMMM[M]); format = format.replace(new RegExp(MMM[0], 'g'), MMM[M]); format = format.replace(/\\(.)/g, '$1'); return format; }; function whoisMessage(whoisData, buffer) { let messages = []; for(let key in whoisData) { switch(key) { case 'hostmask': messages.push(''+whoisData[key]+': '+validators.escapeHTML(whoisData['realname'])); break; case 'idleSeconds': let msgs = 'is idle for '+whoisData[key]+' seconds'; if(whoisData['signonTime']) msgs += ', signed on at '+new Date(parseInt(whoisData['signonTime'])*1000); messages.push(msgs); break; case 'loggedIn': case 'registered': case 'connectingFrom': case 'usingModes': case 'title': messages.push(validators.escapeHTML(whoisData[key])); break; case 'channels': messages.push(whoisData[key].join(' ')); break; case 'server': let adfd = 'is on '+whoisData[key]+''; if(whoisData['server_name']) adfd += ' '+validators.escapeHTML(whoisData['server_name'])+''; messages.push(adfd); break; case 'secure': messages.push('is using a secure connection.'); break; case 'bot': messages.push('is a bot on '+irc.serverData[buffer.server].network); break; } } for(let i in messages) { let mesg = '['+whoisData.nickname+'] '+messages[i]; buffer.addMessage(mesg, null, 'whois'); } } function rand(min, max) { return parseInt(Math.random() * (max-min+1), 10) + min; } if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != undefined ? args[number] : match; }); }; } function remove_str(arr, str) { let index = arr.indexOf(str); if(index > -1) { arr.splice(index, 1); return arr; } return arr; }; function grep(items, callback) { let filtered = []; for (let i in items) { let item = items[i]; let cond = callback(item); if (cond) { filtered.push(item); } } return filtered; }; function match(word, array) { return grep( array, function(w) { return w.toLowerCase().indexOf(word.toLowerCase()) == 0; } ); } function linkify(text) { // see http://daringfireball.net/2010/07/improved_regex_for_matching_urls let re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:''.,<>?«»“”‘’]))/gi; let parsed = text.replace(re, function(url) { // turn into a link let href = url; if (url.indexOf('http') !== 0) { href = 'http://' + url; } return '' + url + ''; }); return parsed; } function serialize(obj) { let str = []; for(let p in obj) if (obj.hasOwnProperty(p)) { str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); } return str.join('&'); } function removeClass(element, cl) { let classList = element.className.split(' '); if(classList.indexOf(cl) != -1) classList.splice(classList.indexOf(cl), 1); element.className = classList.join(' '); } function addClass(element, cl) { let classList = element.className.split(' '); if(classList.indexOf(cl) != -1) return; classList.push(cl); element.className = classList.join(' '); } function toggleClass(element, cl) { let classList = element.className.split(' '); if(classList.indexOf(cl) != -1) removeClass(element, cl); else addClass(element, cl); } function objectGetKey(obj, value) { let key = null; for(let f in obj) { if(obj[f] == value) key = f; } return key; } let composer = { message: { simple: function(time, sender, message, type) { let element = document.createElement('div'); element.className = 'message type_simple m_'+type; if(irc.config.timestamps) element.innerHTML += ''+time.format(irc.config.timestampFormat)+' '; if(irc.config.colors) message = stylize(message); else message = colorizer.strip(message); message = linkify(message); switch(type) { case 'mode': element.innerHTML += ' '+sender+' '; element.innerHTML += ''+message+''; break; case 'action': element.innerHTML += ' '+sender+' '; element.innerHTML += ''+message+''; break; case 'part': case 'quit': case 'kick': element.innerHTML += ' '+sender+''; element.innerHTML += ' '+message+''; break; case 'join': element.innerHTML += ' '+sender+''; element.innerHTML += ' '+message+''; break; case 'ctcp_response': element.innerHTML += ' CTCP response from '+sender+' '; element.innerHTML += ''+message+''; break; case 'ctcp_request': element.innerHTML += ' CTCP request to '+sender+' '; element.innerHTML += ''+message+''; break; default: if(sender) { element.innerHTML += ''+sender+' '+message+''; } else { element.innerHTML += ''+message+''; addClass(element, 'no_sender'); } break; } if(irc.config.colorNicknames == true) { if(sender) { let sndr1 = element.querySelector('.sender'); if(sndr1) sndr1.style.color = colorizer.get_random_color(sndr1.innerHTML); } let sndr2 = element.querySelectorAll('.nick'); if(sndr2.length > 0) for(let a in sndr2) if(sndr2[a] && sndr2[a]['style']) sndr2[a].style.color = colorizer.get_random_color(sndr2[a].innerHTML); } return element; } }, theme_selection: function(name, theme) { let btn = document.createElement('div'); btn.className = 'theme_button theme_'+theme.type; let sampler = document.createElement('div'); sampler.className = 'sampler'; sampler.style['background-color'] = theme.colorSamples.background; let toolbar = document.createElement('span'); toolbar.className = 's_toolbar'; toolbar.style['background-color'] = theme.colorSamples.toolbar; let nameb = document.createElement('span'); nameb.className = 'name'; nameb.innerHTML = theme['name']; sampler.appendChild(toolbar); btn.appendChild(sampler); btn.appendChild(nameb); btn.setAttribute('id', 'theme-'+name); return btn; }, embedURL: function(server, port, defaultNick, useSSL, hideServerData, channels) { let builder = window.location.origin + '/'; let params = {}; let final_channels = []; if(server) builder += server + '/'; if(channels) channels = channels.trim(); if(defaultNick) params.nickname = defaultNick; if(port && port != 6667) params.port = port; if(useSSL) params.secure = 1; if(hideServerData) params.server_data = 0; if(Object.keys(params).length > 0) builder += '?' + serialize(params); if(channels) { if(channels.indexOf(',')) { let tmp = channels.split(','); for(let i in tmp) { tmp[i] = tmp[i].trim(); if(tmp[i].indexOf('#') != 0) tmp[i] = '#'+tmp[i]; final_channels.push(tmp[i]); } } else { if(channels.indexOf('#') != 0) channels = '#'+channels; final_channels.push(channels); } builder += final_channels.join(','); } return builder; } }; /*****************************\ |** **| |** CLIENT COMMANDS **| |** **| \*****************************/ // commandName: {execute: function(buffer, handler, command, message, listargs) {}, description: ''} let commands = { embed: {execute: function(buffer, handler, command, message, listargs) { let data = irc.auther.getDataFromForm(); let url = 'Couldn\'t compose URL!'; if(data) url = composer.embedURL(data.server, data.port, data.nickname, data.secure, true, data.autojoin.join(',')); buffer.addMessage(url, null, 'help'); }, description: '- Embed URL for the current connection'}, join: {execute: function(buffer, handler, command, message, listargs) { if (!listargs[1]) { if(!buffer.alive) { irc.socket.emit('userinput', {command: 'join', server: buffer.server, message: '', arguments: [buffer.name]}); } else { handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); } } else { irc.socket.emit('userinput', {command: 'join', server: buffer.server, message: '', arguments: [listargs[1]]}); } }, description: ' - Join a channel'}, part: {execute: function(buffer, handler, command, message, listargs) { if (!listargs[1] && buffer.type == 'channel') { irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: '', arguments: [buffer.name]}); } else if(buffer.type != 'channel') { handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); } else if(listargs[1]) { if(listargs[1].indexOf('#') != -1) { let msg = ''; if(listargs[2]) msg = listargs.slice(2).join(' '); irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: msg, arguments: [listargs[1]]}); } else { if(buffer.type == 'channel') { irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name]}); } else { handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); } } } }, description: '[<#channel>|] [message] - Leave the channel. If no channel specified, leave the current buffer.', aliases: ['leave']}, topic: {execute: function(buffer, handler, command, message, listargs) { if (!listargs[1] && buffer.type == 'channel') { irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: '', arguments: [buffer.name]}); } else if(buffer.type != 'channel') { handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); } else if(listargs[1]) { if(listargs[1].indexOf('#') != -1) { let msg = ''; if(listargs[2]) msg = listargs.slice(2).join(' '); irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: msg, arguments: [listargs[1]]}); } else { if(buffer.type == 'channel') { irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name]}); } else { handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); } } } }, description: '[<#channel>|] [topic] - Sets/prints the current topic of the channel.', aliases: ['t']}, kick: {execute: function(buffer, handler, command, message, listargs) { if(buffer.type != 'channel') return handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel!'); if(!listargs[1]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameter !'); irc.socket.emit('userinput', {command: 'kick', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [buffer.name, listargs[1]]}); }, description: ' - Kick the following user from the channel.'}, quit: {execute: function(buffer, handler, command, message, listargs) { irc.socket.emit('userinput', {command: 'quit', server: buffer.server, message: listargs.slice(1).join(' '), arguments: []}); }, description: '[] - Quit the current server with message.', aliases: ['exit']}, mode: {execute: function(buffer, handler, command, message, listargs) { irc.socket.emit('userinput', {command: 'mode', server: buffer.server, message: listargs.slice(1).join(' '), arguments: []}); }, description: '[target] [mode] - Set/remove mode of target.'}, msg: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1] || !listargs[2]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); if(listargs[1] == '*') listargs[1] = buffer.name; irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [listargs[1]]}); }, description: ' - Sends a message to target.', aliases: ['privmsg', 'q', 'query', 'say']}, ctcp: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1] || !listargs[2]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); if(listargs[1] == '*') listargs[1] = buffer.name; listargs[2] = listargs[2].toUpperCase(); irc.socket.emit('userinput', {command: 'ctcp', server: buffer.server, message: listargs.slice(2).join(' '), arguments: listargs.slice(1)}); }, description: ' [arguments] - Sends a CTCP request to target.'}, notice: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1] || !listargs[2]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); if(listargs[1] == '*') listargs[1] = buffer.name; irc.socket.emit('userinput', {command: 'notice', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [listargs[1]]}); }, description: ' - Sends a NOTICE to target.'}, action: {execute: function(buffer, handler, command, message, listargs) { irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: '\x01ACTION '+message.substring(command.length+2)+'\x01', arguments: [buffer.name]}); }, description: ' - act as yourself', aliases: ['me']}, list: {execute: function(buffer, handler, command, message, listargs) { irc.socket.emit('userinput', {command: 'list', server: buffer.server, message: '', arguments: listargs.splice(1)}); }, description: '- List all channels on the current server.'}, nick: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1]) { if(buffer.server != '') { let mynick = irc.serverData[buffer.server].my_nick; buffer.addMessage('Your nickname is: '+mynick+'', null, 'help'); } return; } irc.socket.emit('userinput', {command: 'nick', server: buffer.server, message: '', arguments: listargs.splice(1)}); }, description: '- List all channels on the current server.', aliases: ['nickname']}, names: {execute: function(buffer, handler, command, message, listargs) { let channel = ''; if(!listargs[1]) { if(buffer.type == 'channel') channel = buffer.name; else return handler.commandError(buffer, '/'+cmd.toUpperCase()+': Buffer is not a channel!'); } else if(listargs[1].indexOf('#') == 0) { channel = listargs[1]; } else { return handler.commandError(buffer, '/'+cmd.toUpperCase()+': Invalid channel name!'); } irc.socket.emit('userinput', {command: 'names', server: buffer.server, message: '', arguments: [channel]}); }, description: '- List all users on the current channel.', aliases: ['nicknames']}, quote: {execute: function(buffer, handler, command, message, listargs) { irc.socket.emit('userinput', {command: listargs[1], server: buffer.server, message: listargs.slice(2).join(' '), arguments: listargs.splice(2)}); }, description: ' [args] - Send a raw command to the server.', aliases: ['raw']}, whois: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); irc.socket.emit('userinput', {command: 'whois', server: buffer.server, message: '', arguments: [listargs[1]]}); }, description: ' - Display information about a user.'}, connect: {execute: function(buffer, handler, command, message, listargs) { clientdom.connector.frame.style.display = 'block'; irc.auther.authMessage('Create a new connection', false); irc.auther.canClose = true; }, description: '- Create a new connection.'}, help: {execute: function(buffer, handler, command, message, listargs) { if(!listargs[1]) return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); let cmd = listargs[1].toLowerCase(); if(cmd.indexOf('/') === 0) cmd = cmd.substring(1); if(cmd in commands) { if('description' in commands[cmd]) buffer.addMessage('/'+cmd.toUpperCase()+' '+ validators.escapeHTML(commands[cmd].description), null, 'help'); else buffer.addMessage('/'+cmd.toUpperCase()+' - No description provided', null, 'help'); } else { let foundAliased = null; for(let cmd2 in commands) { if(!commands[cmd2]['aliases']) continue; if(commands[cmd2].aliases.indexOf(cmd) != -1) foundAliased = cmd2; } if(foundAliased) { if('description' in commands[foundAliased]) buffer.addMessage('/'+cmd.toUpperCase()+' '+ validators.escapeHTML(commands[foundAliased].description), null, 'help'); else buffer.addMessage('/'+cmd.toUpperCase()+' - No description provided', null, 'help'); } else { handler.commandError(buffer, '/'+cmd.toUpperCase()+': Unknown command!'); } } }, description: ' - Display help for command'}, clear: {execute: function(buffer, handler, command, message, listargs) { buffer.clearMessages(); }, description: '- Clears the current buffer.'} }; /*********************\ |** **| |** CLASSES **| |** **| \*********************/ class Nicklist { constructor(buffer) { this.buffer = buffer; this.nicks = []; this.simplifiedNicksList = []; } sort() { let spfx = irc.serverData[this.buffer.server].supportedPrefixes; this.nicks.sort(function (a,b) { let rex = new RegExp('^['+spfx+']'); let nicks = [a.prefix.replace(rex,'').toLowerCase(), b.prefix.replace(rex,'').toLowerCase()]; let prefix = []; if (rex.test(a.prefix)) prefix.push(spfx.indexOf(a.prefix[0])); else prefix.push(spfx.length+1); if (rex.test(b.prefix)) prefix.push(spfx.indexOf(b.prefix[0])); else prefix.push(spfx.length+1); if (prefix[0] < prefix[1]) return -1; if (prefix[0] > prefix[1]) return 1; if (nicks[0] > nicks[1]) return 1; if (nicks[0] < nicks[1]) return -1; return 0; }); return this.nicks; } appendToList(nick) { if(!this.buffer.active) return; let str = document.createElement('div'); str.className = 'nick'; str.setAttribute('id', 'nick-'+nick.nickname); let construct = ''; if(nick.prefix != '') construct += ''+nick.prefix+''; else construct += ' '; if(irc.config.colorNicklist) construct += ''+nick.nickname+''; else construct += ''+nick.nickname+''; str.innerHTML = construct; clientdom.nicklist.appendChild(str); } render() { if(!this.buffer.active) return; clientdom.nicklist.innerHTML = ''; this.simplifiedNicksList = []; this.sort(); for(let n in this.nicks) { let nick = this.nicks[n]; this.simplifiedNicksList.push(nick.nickname); this.appendToList(nick); } irc.chat.input_handler.searchNicknames = this.simplifiedNicksList; } nickAdd(nickname) { let newbie = { nickname: nickname, prefix: '', modes: [] }; if(this.getNickIndex(nickname) != null) return; this.nicks.push(newbie); this.render(); } nickAddObject(obj) { if(this.getNickIndex(obj.nickname) != null) return; this.nicks.push(obj); } nickRemove(nickname) { let nickIndex = this.getNickIndex(nickname); if(nickIndex != null) this.nicks.splice(nickIndex, 1); else return; if(!this.buffer.active) return; let tt = clientdom.nicklist.querySelector('#nick-'+nickname); if(tt) tt.remove(); remove_str(this.simplifiedNicksList, nickname); } nickChange(oldNickname, newNickname) { let nickIndex = this.getNickIndex(oldNickname); if(nickIndex != null) this.nicks[nickIndex].nickname = newNickname; else return; this.render(); } getNickIndex(nickname) { let result = null; for(let n in this.nicks) if(this.nicks[n].nickname == nickname) result = n; return result; } modeAdded(nickname, newMode) { let nickIndex = this.getNickIndex(nickname); let nick = null; if(nickIndex != null) nick = this.nicks[nickIndex]; else return; let modeTranslations = irc.serverData[this.buffer.server].modeTranslation; let prefixes = irc.serverData[this.buffer.server].supportedPrefixes; nick.modes.push(newMode); for(let mode in modeTranslations) { let prefix = modeTranslations[mode]; if(nick.modes.indexOf(mode) == -1) continue; let a = nick.modes.indexOf(mode) - 1; if(a >= 0) { if(prefixes.indexOf(modeTranslations[nick.modes[a]]) < prefixes.indexOf(prefix)) { nick.prefix = modeTranslations[nick.modes[a]]; break; } } else { nick.prefix = prefix; break; } } this.render(); } modeRemoved(nickname, oldMode) { let nickIndex = this.getNickIndex(nickname); let nick = null; if(nickIndex != null) nick = this.nicks[nickIndex]; else return; let modeTranslations = irc.serverData[this.buffer.server].modeTranslation; let prefixes = irc.serverData[this.buffer.server].supportedPrefixes; remove_str(nick.modes, oldMode); let currentLowest = ''; for(let n in nick.modes) { let mode = nick.modes[n]; let nextMode = nick.modes[n+1]; if(!nextMode && mode) { currentLowest = modeTranslations[mode]; break; } else if(nextMode) { if(prefixes.indexOf(modeTranslations[nextMode]) > prefixes.indexOf(modeTranslations[mode])) currentLowest = modeTranslations[nextMode]; } else { break; } } nick.prefix = currentLowest; this.render(); } } class Tab { constructor(buffer) { this.buffer = buffer; this.element = null; this.closeRequested = false; } // Create a tab element renderTab() { let internals = ''+ this.buffer.title +''; let ttt = document.createElement('div'); ttt.innerHTML = internals; ttt.className = 'tab'; ttt.setAttribute('id', 'tab-'+this.buffer.name); clientdom.tabby.appendChild(ttt); this.element = ttt; ttt.innerHTML += 'x'; ttt.querySelector('#close').addEventListener('click', () => { this.close(); }, false); ttt.addEventListener('click', () => { if(this.closeRequested) return; if(this.buffer.active) return; irc.chat.render(this.buffer); }, false); } setActive(active) { if(this.element) { this.element.className = 'tab'; if(active) { addClass(this.element, 'active'); } } } setHot(hot) { if(this.element) { if(hot) addClass(this.element, 'hot'); else removeClass(this.element, 'hot'); } } setUnreadCount(count) { if(this.element) { let counter = this.element.querySelector('#unread'); if(count == 0) { counter.className = 'none'; } else { counter.innerHTML = count; counter.className = ''; } } } setTitle(title) { let titleEl = this.element.querySelector('#title'); if(titleEl) titleEl.innerHTML = title; } close() { this.closeRequested = true; this.buffer.closeChatBuffer(); } } class ChatBuffer { constructor(servername, buffername, tabname, type) { this.messages = []; this.nicklist = null; this.topic = null; this.input = ''; this.lastscroll = 0; this.wasAtBottom = false; this.unreadCount = 0; this.server = servername; this.name = buffername; this.title = tabname; this.type = type; this.active = false; this.alive = true; this.hot = false; if(type != 'applet') { this.tab = new Tab(this); this.tab.renderTab(); } if(type == 'channel') this.nicklist = new Nicklist(this, clientdom.nicklist); } render() { this.active = true; this.tab.setActive(true); this.unreadCount = 0; this.tab.setUnreadCount(0); clientdom.chat.className = 'chatarea'; clientdom.nicklist.innerHTML = ''; clientdom.topicbar.innerHTML = ''; if(!irc.config.sharedInput) clientdom.input.value = this.input; if(this.nicklist) { addClass(clientdom.chat, 'vnicks'); this.nicklist.render(); } if(this.hot) this.setHotStatus(false); if(this.topic != null && this.topic != '') { addClass(clientdom.chat, 'vtopic'); if(irc.config.colors) clientdom.topicbar.innerHTML = linkify(stylize(this.topic)); else clientdom.topicbar.innerHTML = linkify(colorizer.strip(this.topic)); } this.renderMessages(); if(this.wasAtBottom) clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; else clientdom.letterbox.scrollTop = this.lastscroll; clientdom.currentNickname.innerHTML = irc.serverData[this.server].my_nick; irc.chat.changeTitle('TeemantIRC - '+this.title); } renderMessages() { if(!this.active) return; clientdom.letterbox.innerHTML = ''; for(let t in this.messages) { let mesg = this.messages[t]; this.appendMessage({message: mesg.message, sender: mesg.sender, type: mesg.type, time: mesg.time}); } } clearMessages() { this.messages = []; if(this.active) clientdom.letterbox.innerHTML = ''; } setHotStatus(hot) { this.hot = hot; if(this.tab) this.tab.setHot(hot); } appendMessage(meta) { let mesgConstr = composer.message[irc.chatType](meta.time, meta.sender, meta.message, meta.type); if(irc.serverData[this.server]) { let mynick = irc.serverData[this.server].my_nick; if((meta.type == 'privmsg' || meta.type == 'notice' || meta.type == 'action') && meta.message.toLowerCase().indexOf(mynick.toLowerCase()) != -1 && meta.sender != mynick) addClass(mesgConstr, 'mentioned'); } clientdom.letterbox.appendChild(mesgConstr); let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop; if(lFactor > clientdom.letterbox.scrollHeight - 100) clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; } topicChange(topic) { if(this.active) { if(irc.config.colors) clientdom.topicbar.innerHTML = linkify(stylize(topic)); else clientdom.topicbar.innerHTML = linkify(colorizer.strip(topic)); if(this.topic == null) addClass(clientdom.chat, 'vtopic'); } this.topic = topic; } addMessage(message, sender, type, time) { let mesg = {message: message, sender: sender, type: type, time: time || new Date()}; this.messages.push(mesg); if(this.active) { this.appendMessage(mesg); } else { this.unreadCount += 1; if(irc.serverData[this.server]) { let mynick = irc.serverData[this.server].my_nick; if((type == 'privmsg' || type == 'notice' || type == 'action') && message.toLowerCase().indexOf(mynick.toLowerCase()) != -1 && sender != mynick) { // TODO: notify user of mentioned if(!this.active) this.setHotStatus(true); } } } this.tab.setUnreadCount(this.unreadCount); } switchOff() { irc.chat.input_handler.searchNicknames = []; let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop; if(lFactor > clientdom.letterbox.scrollHeight - 100) this.wasAtBottom = true; else this.wasAtBottom = false; if(!irc.config.sharedInput) this.input = clientdom.input.value; this.tab.setActive(false); this.lastscroll = clientdom.letterbox.scrollTop; this.active = false; } setAliveStatus(status) { this.alive = status; if(this.alive) this.tab.setTitle(this.title); else this.tab.setTitle('('+this.title+')'); } closeChatBuffer() { irc.chat.closeChatBuffer(this); } } class ThemeSelector { constructor(settings, variable) { this.settings = settings; this.variable = variable; } set_active_selection(name) { let all = clientdom.settings.available_themes.querySelectorAll('.theme_button'); if(all.length > 0) { for(let a in all) { if(all[a] && all[a]['className']) { let elem = all[a]; if(elem.getAttribute('id') == 'theme-'+name) { addClass(elem, 'selected'); } else { removeClass(elem, 'selected'); } } } } } bindButton(button, theme) { button.onclick = (e) => { this.settings.themeSelection = theme; this.set_active_selection(theme); }; } render() { clientdom.settings.available_themes.innerHTML = ''; for(let n in themes.available) { let theme = themes.available[n]; let button = composer.theme_selection(n, theme); clientdom.settings.available_themes.appendChild(button); this.bindButton(button, n); } } } class AppletChatBuffer extends ChatBuffer { constructor(appletName, title, frame) { super('', appletName, title, 'applet'); this.tab = null; this.isOpen = false; this.timeout = null; this.frame = frame; } closeChatBuffer() { irc.chat.closeChatBuffer(this); this.tab = null; this.isOpen = false; } open() { if(this.isOpen) { irc.chat.render(this); return; } this.tab = new Tab(this); this.tab.renderTab(); irc.chat.buffers.push(this); irc.chat.render(this); this.isOpen = true; } addMessage(message, sender, type, time) { // Don't send messages to any applet buffer return; } switchOff() { this.active = false; this.tab.setActive(false); this.frame.style.display = 'none'; } render() { this.active = true; this.tab.setActive(true); this.frame.style.display = 'block'; } } class Settings extends AppletChatBuffer { constructor() { super('settings', 'Settings', clientdom.settings.frame); this.themeSelection = ''; this.themeSelector = new ThemeSelector(this); this.themeSelector.render(); clientdom.settings.save.onclick = (e) => { this.saveSpecified(); }; clientdom.settings.open.onclick = (e) => { this.open(); }; } switch_theme() { if(this.themeSelection != '') { themes.change_theme(this.themeSelection); irc.config.theme = this.themeSelection; this.themeSelector.set_active_selection(this.themeSelection); } } saveSpecified() { if(this.timeout) clearTimeout(this.timeout); this.switch_theme(); for(let key in irc.config) { let value = irc.config[key]; let type = typeof(value); if(clientdom.settings[key]) { if(type == 'boolean') irc.config[key] = clientdom.settings[key].checked; else irc.config[key] = clientdom.settings[key].value; } } clientdom.settings.saveStatus.innerHTML = 'Settings saved!'; if('localStorage' in window) { window.localStorage['teemant_settings'] = JSON.stringify(irc.config); } this.timeout = setTimeout(function() { clientdom.settings.saveStatus.innerHTML = ''; }, 3000); } setInitialValues() { if('localStorage' in window) { if(window.localStorage['teemant_settings']) { try { let settings = JSON.parse(window.localStorage.teemant_settings); for(let key in irc.config) { let value = irc.config[key]; let type = typeof(value); if(settings[key]) { if(key == 'theme') { this.themeSelection = settings[key]; continue; } if(type == 'boolean') clientdom.settings[key].checked = settings[key]; else clientdom.settings[key].value = settings[key]; } } this.saveSpecified(); return; } catch(e) {} } } for(let key in irc.config) { let value = irc.config[key]; let type = typeof(value); if(clientdom.settings[key]) { if(type == 'boolean') clientdom.settings[key].checked = value; else clientdom.settings[key].value = value; } } } render() { super.render(); clientdom.chat.className = 'chatarea'; clientdom.nicklist.innerHTML = ''; clientdom.topicbar.innerHTML = ''; clientdom.letterbox.innerHTML = ''; irc.chat.changeTitle('TeemantIRC - Settings'); } } class IRCConnector { constructor() { this.formLocked = false; this.canClose = false; clientdom.connector.form.onsubmit = (e) => { if(this.formLocked) { e.preventDefault(); return false; } this.formLocked = true; this.validateForm(e); }; clientdom.connector.onkeyup = (e) => { let key = evt.keyCode || evt.which || evt.charCode || 0; if(key === 27 && this.canClose) this.authComplete(); }; clientdom.connector.pwtrigger.onclick = (e) => { this.togglePassword(); }; } fillFormFromURI() { for(let param in urlParams) { let value = urlParams[param]; switch(param) { case 'nick': case 'nickname': if (validators.nickname(value)) clientdom.connector.nickname.value = value.replace(/\?/g, rand(10000, 99999)); break; case 'secure': case 'ssl': if(value == 'true' || value == '1') clientdom.connector.secure.checked = true; break; case 'password': if(value == 'true' || value == '1') { clientdom.connector.pwtrigger.checked = true; this.togglePassword(); } break; case 'server': case 'host': if(validators.iporhost(value)) clientdom.connector.server.value = value; break; case 'server_data': case 'extra': case 'extras': case 'connection': if(value == 'false' || value == '0') clientdom.connector.server_data.style.display = 'none'; break; case 'port': try { let ppp = parseInt(value); clientdom.connector.port.value = ppp; } catch(e) {} break; } } if(window.location.hash) clientdom.connector.channel.value = window.location.hash; if(window.location.pathname.length > 4) { let t1 = window.location.pathname.substring(1, window.location.pathname.length-1); let proposed = ''; if(t1.indexOf('/') != -1) { proposed = t1.split('/'); proposed = proposed[proposed.length-1]; } else { proposed = t1; } if(validators.iporhost(proposed)) clientdom.connector.server.value = proposed; } } getDataFromForm() { let nickname = clientdom.connector.nickname.value; let password = clientdom.connector.password.value; let channel = clientdom.connector.channel.value; let server = clientdom.connector.server.value; let port = clientdom.connector.port.value; if (!validators.nickname(nickname)) { this.authMessage('Erroneous nickname!', true); return false; } if (channel.indexOf(',') !== -1) { channel = channel.trim().split(','); for (let t in channel) { let chan = channel[t]; channel[t] = chan.trim(); if (chan.indexOf('#') != 0) { channel[t] = '#'+chan; } } } else if(channel != '') { channel = channel.trim(); if (channel.indexOf('#') != 0) { channel = '#'+channel; } channel = [channel]; } else { channel = []; } if(!validators.iporhost(server)) { this.authMessage('Erroneous server address!', true); return false; } try { port = parseInt(port); } catch(e) { this.authMessage('Erroneous port!', true); return false; } if(port < 10 || port > 65535) { this.authMessage('Erroneous port!', true); return false; } if(!clientdom.connector.pwtrigger.checked) password = ''; return {nickname: nickname, autojoin: channel, server: server, port: port, password: password, secure: clientdom.connector.secure.checked }; } validateForm(event) { event.preventDefault(); let data = this.getDataFromForm(); if(!data) return; irc.socket.emit('irc_create', data); return true; } authMessage(message, error) { clientdom.connector.messenger.innerHTML = ''+message+''; if(error) this.formLocked = false; } togglePassword() { if(clientdom.connector.pwtrigger.checked) clientdom.connector.pwbox.style.display = 'block'; else clientdom.connector.pwbox.style.display = 'none'; } authComplete() { clientdom.connector.frame.style.display = 'none'; this.formLocked = false; } } class InputHandler { constructor() { this.history = []; this.historyCaret = 0; this.searchNicknames = []; this.index = -1; this.words = []; this.last = ''; clientdom.input.onkeyup = (evt) => { let key = evt.keyCode || evt.which || evt.charCode || 0; if (key == 13) { this.handleInput(); return; } this.keyUpHandle(evt, key); }; clientdom.input.onkeydown = (e) => { let key = e.keyCode || e.which || e.charCode || 0; if (e.ctrlKey || e.shiftKey || e.altKey) { return; } this.keyDownHandle(e, key); }; clientdom.input.onfocus = (e) => { if(irc.config.scrollOnFocus) clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; }; clientdom.send.onclick = (e) => { this.handleInput(); }; } keyUpHandle(e, key) { if(key == 38 || key == 40) { clientdom.input.selectionStart = clientdom.input.value.length; clientdom.input.selectionEnd = clientdom.input.value.length; } else if(key == 9) return; let input = clientdom.input.value; let word = input.split(/ |\n/).pop(); // Reset iteration. this.tabCompleteReset(); // Check for matches if the current word is the last word. if (clientdom.input.selectionStart == input.length && word.length) { // Call the match() function to filter the words. this.tabWords = match(word, this.searchNicknames); if(input.indexOf(word) == 0) for(let n in this.tabWords) this.tabWords[n] += ': '; } } keyDownHandle(e, key) { if(key == 38) { if(this.historyCaret <= 0) { this.historyCaret = 0; } else { this.historyCaret -= 1; } let selection = this.history[this.historyCaret]; if(selection) { clientdom.input.value = selection; this.tabCompleteReset(); } return; } else if(key == 40) { if(this.historyCaret >= this.history.length) { this.historyCaret = this.history.length; } else { this.historyCaret += 1; } let selection = this.history[this.historyCaret]; if(!this.history[this.historyCaret]) selection = ''; clientdom.input.value = selection; this.tabCompleteReset(); return; } if(key == 9) { e.preventDefault(); this.index++; // Get next match. let word = this.tabWords[this.index % this.tabWords.length]; if (!word) { return; } let value = clientdom.input.value; this.lastWord = this.lastWord || value.split(/ |\n/).pop(); // Return if the 'minLength' requirement isn't met. if (this.lastWord.length < 1) return; let text = value.substr(0, clientdom.input.selectionStart - this.lastWord.length) + word; clientdom.input.value = text; // Remember the word until next time. this.lastWord = word; return; } } tabCompleteReset() { this.index = -1; this.lastWord = ''; this.tabWords = []; } handleInput() { let message = clientdom.input.value; let buffer = irc.chat.getActiveChatBuffer(); if(!buffer) return; if(message.trim() == '') return; let listargs = message.split(' '); if(listargs[0].indexOf('/') == 0) { let command = listargs[0].substring(1).toLowerCase(); if(command.toLowerCase() in commands) { let cmd = commands[command]; if('execute' in cmd) cmd.execute(buffer, this, command, message, listargs); } else { let foundAliased = null; for(let cmd in commands) { if(!commands[cmd]['aliases']) continue; if(commands[cmd].aliases.indexOf(command) != -1) foundAliased = commands[cmd]; } if(foundAliased) foundAliased.execute(buffer, this, command, message, listargs); else this.commandError(buffer, listargs[0].toUpperCase()+': Unknown command!'); } } else { irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: message, arguments: [buffer.name]}); } this.history.push(message); this.historyCaret = this.history.length; clientdom.input.value = ''; } commandError(buffer, message) { buffer.addMessage(message, null, 'error'); return true; } } class IRCChatWindow { constructor() { this.buffers = []; this.firstServer = true; this.currentChatBuffer = null; this.input_handler = new InputHandler(); clientdom.frame.style.display = 'none'; clientdom.smsctrig.onclick = (e) => { toggleClass(clientdom.chat, 'vopentrig'); }; } destroyAllChatBuffers() { // Wipe all server data irc.serverData = {}; irc.serverChatQueue = {}; this.buffers = []; // Clear tabs clientdom.tabby.innerHTML = ''; // Reset to the defaults irc.auther.authMessage('Disconnected', true); clientdom.frame.style.display = 'none'; this.firstServer = true; window.onbeforeunload = null; } getChatBufferByName(buffername) { let result = null; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.name.toLowerCase() == buffername.toLowerCase()) result = buf; } return result; } getActiveChatBuffer() { let result = null; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.active == true) result = buf; } return result; } getServerChatBuffer(server) { let result = null; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.server == server) result = buf; } return result; } getChatBuffersByServer(server) { let result = []; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.server == server) result.push(buf); } return result; } getChatBufferByServerName(server, channel) { let result = null; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.server == server && buf.name.toLowerCase() == channel.toLowerCase()) result = buf; } return result; } getChatBuffersByType(type) { let result = []; for (let t in this.buffers) { let buf = this.buffers[t]; if(buf.type == type) result.push(buf); } return result; } newServerChatBuffer(serverinfo) { if(this.firstServer) { clientdom.frame.style.display = 'block'; window.onbeforeunload = function(e) { return 'IRC will disconnect.'; }; } let prefixes = ''; for(let v in serverinfo.supportedModes) { prefixes += serverinfo.supportedModes[v]; } irc.serverData[serverinfo.address] = { modeTranslation: serverinfo.supportedModes, supportedPrefixes: prefixes, network: serverinfo.network, my_nick: serverinfo.nickname, max_channel_length: serverinfo.max_channel_length }; let newServer = new ChatBuffer(serverinfo.address, serverinfo.address, serverinfo.network, 'server'); this.buffers.push(newServer); this.render(newServer); this.firstServer = false; if(irc.serverChatQueue[serverinfo.address]) { for(let a in irc.serverChatQueue[serverinfo.address]) { let mesg = irc.serverChatQueue[serverinfo.address][a]; newServer.addMessage(mesg.message, mesg.from, mesg.type, mesg.time); } delete irc.serverChatQueue[serverinfo.address]; } } createChatBuffer(server, name, type, autoswitch) { let buf = this.getChatBufferByServerName(server, name); if(buf) { if(autoswitch) this.render(buf); return buf; } buf = new ChatBuffer(server, name, name, type); this.buffers.push(buf); if(autoswitch) this.render(buf); return buf; } closeChatBuffer(buffer) { if(buffer.type == 'server') irc.socket.emit('userinput', {command: 'quit', server: buffer.server, message: 'Server tab closed', arguments: []}); else if(buffer.type == 'channel' && buffer.alive) irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: 'Tab closed', arguments: [buffer.name]}); let bufIndex = this.buffers.indexOf(buffer); if(buffer.active) { if (bufIndex == 0) { if(this.buffers[bufIndex+1]) { this.render(this.buffers[bufIndex+1]); } } else { this.render(this.buffers[bufIndex-1]); } } buffer.tab.element.remove(); this.buffers.splice(bufIndex, 1); if(this.buffers.length == 0 || (this.buffers.length == 1 && this.buffers[0].type == 'applet')) { irc.chat.destroyAllChatBuffers(); irc.auther.authMessage('Create a new connection', false); irc.auther.canClose = false; clientdom.connector.frame.style.display = 'block'; } } messageChatBuffer(name, server, message) { let buf = this.getChatBufferByServerName(server, name); if(buf == null) buf = this.createChatBuffer(server, name, 'message', false); if(message.type == 'privmsg' && message.message.indexOf('\x01ACTION') == 0) { message.message = message.message.substring(8); message.type = 'action'; } buf.addMessage(message.message, message.from, message.type); } buildNicklist(channel, server, nicks) { let buf = this.getChatBufferByServerName(server, channel); if(buf == null) return; let channelSendNicks = []; for(let n in nicks) { let nick = {nickname: '', prefix: '', modes: []}; if(irc.serverData[buf.server].supportedPrefixes.split('').indexOf(nicks[n].substring(0, 1)) != -1) { nick.prefix = nicks[n].substring(0, 1); nick.nickname = nicks[n].substring(1); nick.modes = [objectGetKey(irc.serverData[buf.server].modeTranslation, nick.prefix)]; channelSendNicks.push('{0}{1}'.format(nick.prefix, nick.nickname)); } else { nick.nickname = nicks[n]; channelSendNicks.push('{1}'.format(nick.prefix, nick.nickname)); } buf.nicklist.nickAddObject(nick); } buf.addMessage('Nicks {0}: {1}'.format(channel, channelSendNicks.join(', ')), null, 'names'); if(buf.active) buf.nicklist.render(); } nickChange(server, oldNick, newNick) { let buffers = this.getChatBuffersByServer(server); if(irc.serverData[server].my_nick == oldNick) { irc.serverData[server].my_nick = newNick; let activeBuf = this.getActiveChatBuffer(); if(activeBuf.server == server) { clientdom.currentNickname.innerHTML = newNick; } } for(let i in buffers) { let buffer = buffers[i]; if(buffer.type != 'channel') continue; if(buffer.nicklist.getNickIndex(oldNick) == null) continue; buffer.nicklist.nickChange(oldNick, newNick); buffer.addMessage('{0} is now known as {1}'.format(oldNick, newNick), null, 'nick'); } } topicChange(channel, server, topic, changer) { let buf = this.getChatBufferByServerName(server, channel); if (!buf) return; buf.topicChange(topic); if(changer) buf.addMessage('{0} has changed the topic of {1} to \'{2}\''.format(changer, channel, topic), null, 'topic'); else buf.addMessage('Topic of {0} is \'{1}\''.format(channel, topic), null, 'topic'); } handleQuit(server, user, reason) { let buffers = this.getChatBuffersByServer(server); for(let i in buffers) { let buffer = buffers[i]; if(buffer.type != 'channel') continue; if(buffer.nicklist.getNickIndex(user.nickname) == null) continue; buffer.nicklist.nickRemove(user.nickname); buffer.addMessage('{0}@{1} has quit {2}'.format(user.username, user.hostname, reason), user.nickname, 'quit'); } } handleJoin(server, user, channel) { let buffer = this.getChatBufferByServerName(server, channel); if(!buffer) return; if(user.nickname == irc.serverData[server].my_nick) buffer.setAliveStatus(true); else buffer.nicklist.nickAdd(user.nickname); buffer.addMessage('{0}@{1} has joined {2}'.format(user.username, user.hostname, channel), user.nickname, 'join'); } handleLeave(server, user, channel, reason, kicker) { let buffer = this.getChatBufferByServerName(server, channel); if(!buffer) return; if(user['nickname']) { if(user.nickname == irc.serverData[server].my_nick) buffer.setAliveStatus(false); } else { if(user == irc.serverData[server].my_nick) buffer.setAliveStatus(false); } if(kicker) buffer.addMessage('has kicked {0} {1}'.format(user, reason), kicker.nickname, 'part'); else buffer.addMessage('{0}@{1} has left {2}{3}'.format(user.username, user.hostname, (reason != null ? ' '+reason+'' : '')), user.nickname, 'part'); if(kicker) buffer.nicklist.nickRemove(user); else buffer.nicklist.nickRemove(user.nickname); } handleMode(data) { let buf = null; if(data.target == irc.serverData[data.server].my_nick) buf = this.getServerChatBuffer(data.server); else buf = this.getChatBufferByServerName(data.server, data.target); if(!buf) return; if(data.type == 'mode_add') { buf.nicklist.modeAdded(data.modeTarget, data.mode); buf.addMessage('set mode {0} +{1} {2}'.format(data.target, data.mode, data.modeTarget), data.user.nickname, 'mode'); } else if(data.type == 'mode_del') { buf.nicklist.modeRemoved(data.modeTarget, data.mode); buf.addMessage('set mode {0} -{1} {2}'.format(data.target, data.mode, data.modeTarget), data.user.nickname, 'mode'); } else { buf.addMessage('set mode {0} {1}'.format(data.target, data.message), data.user.nickname, 'mode'); } } joinChannels(server, channel) { if (channel.indexOf(',') !== -1) { channel = channel.trim().split(','); for (let t in channel) { let chan = channel[t]; channel[t] = chan.trim(); if (chan.indexOf('#') != 0) { channel[t] = '#'+chan; } } } else if(channel != '') { channel = channel.trim(); if (channel.indexOf('#') != 0) { channel = '#'+channel; } channel = [channel]; } else { channel = []; } irc.socket.emit('userinput', {command: 'join', server: server, message: '', arguments: channel}); } changeTitle(title) { // TODO: notify of hot buffers document.title = title; irc.documentTitle = title; } render(buffer) { let activeNow = this.getActiveChatBuffer(); this.input_handler.tabCompleteReset(); if(activeNow) activeNow.switchOff(); buffer.render(); } } /**************************\ |** **| |** INITIALIZATION **| |** **| \**************************/ function parseURL() { let match, pl = /\+/g, // Regex for replacing addition symbol with a space search = /([^&=]+)=?([^&]*)/g, decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); }, query = window.location.search.substring(1); urlParams = {}; while (match = search.exec(query)) urlParams[decode(match[1])] = decode(match[2]); irc.auther.fillFormFromURI(); } function stopWarnings() { if(Object.keys(irc.serverData).length == 0) window.onbeforeunload = null; } window.onpopstate = parseURL; window.onresize = function() { if(irc.config.scrollOnResize) clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; }; window.onload = function() { irc.primaryFrame = document.querySelector('.ircclient'); clientdom.settings['frame'] = irc.primaryFrame.querySelector('.settings'); for(let key in irc.config) { clientdom.settings[key] = clientdom.settings.frame.querySelector('#s_'+key); } clientdom.settings['available_themes'] = clientdom.settings.frame.querySelector('.available_themes'); clientdom.settings['save'] = clientdom.settings.frame.querySelector('#save_settings'); clientdom.settings['saveStatus'] = clientdom.settings.frame.querySelector('#settings_status'); clientdom.connector['frame'] = irc.primaryFrame.querySelector('#authdialog'); clientdom.connector['server_data'] = clientdom.connector.frame.querySelector('.server_data'); clientdom.connector['messenger'] = clientdom.connector.frame.querySelector('#connmsg'); clientdom.connector['form'] = clientdom.connector.frame.querySelector('#IRCConnector'); clientdom.connector['nickname'] = clientdom.connector.form.querySelector('#nickname'); clientdom.connector['password'] = clientdom.connector.form.querySelector('#password'); clientdom.connector['pwtrigger'] = clientdom.connector.form.querySelector('#password_trig'); clientdom.connector['pwbox'] = clientdom.connector.form.querySelector('.password_box'); clientdom.connector['channel'] = clientdom.connector.form.querySelector('#channel'); clientdom.connector['server'] = clientdom.connector.form.querySelector('#server'); clientdom.connector['port'] = clientdom.connector.form.querySelector('#port'); clientdom.connector['secure'] = clientdom.connector.form.querySelector('#secure'); clientdom['tabby'] = irc.primaryFrame.querySelector('.tabby'); clientdom['frame'] = irc.primaryFrame.querySelector('#chat'); clientdom['letterbox'] = clientdom.frame.querySelector('.letterbox'); clientdom['nicklist'] = clientdom.frame.querySelector('.nicklist'); clientdom['currentNickname'] = clientdom.frame.querySelector('.my_nickname'); clientdom['input'] = clientdom.frame.querySelector('.userinput'); clientdom['send'] = clientdom.frame.querySelector('.sendbutton'); clientdom['chat'] = clientdom.frame.querySelector('.chatarea'); clientdom['topicbar'] = clientdom.chat.querySelector('.topicbar'); clientdom['smsctrig'] = clientdom.chat.querySelector('.smsc-nicklistbtn'); clientdom.settings['open'] = irc.primaryFrame.querySelector('.open_settings'); irc.socket = io.connect(); irc.settings = new Settings(); irc.auther = new IRCConnector(); irc.chat = new IRCChatWindow(); parseURL(); irc.settings.setInitialValues(); irc.socket.on('connect', function (data) { irc.socketUp = true; }); irc.socket.on('disconnect', function (data) { irc.socketUp = false; irc.chat.destroyAllChatBuffers(); clientdom.connector.frame.style.display = 'block'; }); // Does everything irc.socket.on('act_client', function (data) { if(data['message']) data.message = validators.escapeHTML(data.message); if(data['reason']) data.reason = validators.escapeHTML(data.reason); switch(data.type) { case 'event_connect': irc.auther.authComplete(); irc.chat.newServerChatBuffer(data); break; case 'event_join_channel': if(data.user.nickname == irc.serverData[data.server].my_nick) irc.chat.createChatBuffer(data.server, data.channel, 'channel', true); irc.chat.handleJoin(data.server, data.user, data.channel); break; case 'event_kick_channel': irc.chat.handleLeave(data.server, data.kickee, data.channel, data.reason, data.user); break; case 'event_part_channel': irc.chat.handleLeave(data.server, data.user, data.channel, data.reason); break; case 'event_quit': irc.chat.handleQuit(data.server, data.user, data.reason); break; case 'event_server_quit': let serverz = irc.chat.getChatBuffersByServer(data.server); for(let a in serverz) { let serv = serverz[a]; serv.addMessage('You are no longer talking on this server.', null, 'error'); serv.setAliveStatus(false); } if(irc.serverData[data.server]) delete irc.serverData[data.server]; stopWarnings(); break; case 'message': if(data.to == irc.serverData[data.server].my_nick) { irc.chat.messageChatBuffer(data.user.nickname, data.server, {message: data.message, type: data.messageType, from: data.user.nickname}); } else if(data.to == null) { let atest = irc.chat.getActiveChatBuffer(); if(atest.server != data.server) atest = irc.chat.getServerChatBuffer(data.server); atest.addMessage(data.message, data.user.nickname, data.messageType); } else { irc.chat.messageChatBuffer(data.to, data.server, {message: data.message, type: data.messageType, from: data.user.nickname}); } break; case 'channel_nicks': irc.chat.buildNicklist(data.channel, data.server, data.nicks); break; case 'channel_topic': if(data['topic'] && data['set_by']) irc.chat.topicChange(data.channel, data.server, data.topic, data['set_by']); else if(data['topic']) irc.chat.topicChange(data.channel, data.server, data.topic, null); else if(data['set_by']) irc.chat.messageChatBuffer(data.channel, data.server, {message: 'Topic set by '+data.set_by+' on '+new Date(data.time*1000), type: 'topic', from: null}); break; case 'nick_change': irc.chat.nickChange(data.server, data.nick, data.newNick); break; case 'mode_add': case 'mode_del': case 'mode': irc.chat.handleMode(data); break; case 'server_message': if(data['error']) data.messageType = 'error'; if(irc.chat.getServerChatBuffer(data.server) == null) { if(!irc.serverChatQueue[data.server]) { irc.serverChatQueue[data.server] = []; } else { irc.serverChatQueue[data.server].push({type: data.messageType, message: data.message, from: data.from || null, time: new Date()}); } } else { irc.chat.messageChatBuffer(data.server, data.server, {message: data.message, type: data.messageType, from: data.from || null}); } break; case 'connect_message': irc.auther.authMessage(data.message, data.error); break; case 'whoisResponse': whoisMessage(data.whois, irc.chat.getActiveChatBuffer()); break; case 'listedchan': irc.chat.messageChatBuffer(data.server, data.server, {message: ''+data.channel+''+ ' '+data.users+' '+data.topic+'', type: 'listentry', from: data.from}); break; } }); };