/* global irc, twemoji, globalConfig */ /* eslint-disable no-useless-escape, no-control-regex */ import seedrandom from 'seedrandom' import { el, span, sf, formatDate, removeStr, match, serialize, objectGetKey, rand, addClass, removeClass, toggleClass } from './utility' import stylize from './colorparser' import themes from './theme' import { IRCConnection } from './irc' const gcfg = Object.assign({}, globalConfig) window.irc = { primaryFrame: null, config: { colors: true, channelify: true, sharedInput: false, timestamps: true, timestampFormat: 'HH:mm:ss', colorNicknames: true, colorNicklist: false, scrollOnResize: true, scrollOnFocus: true, emoji: true, theme: 'default' }, serverData: {}, serverChatQueue: {}, chatType: 'simple', documentTitle: 'TeemantIRC', handler: null } const customCTCPs = { VERSION: function (data, connection) { return `TeemantIRC - ${gcfg.description} ver. ${gcfg.version} - https://teemant.icynet.eu/` }, SOURCE: function (data, connection) { return 'https://gitlab.icynet.eu/IcyNetwork/teemant' }, TIME: function (data, connection) { return new Date() } } const clientdom = { connector: {}, settings: {} } const colorizer = { getRandomColor: function (nickname) { let themefunc = themes.available[irc.config.theme].nick_pallete let randgen = seedrandom(nickname) let h = rand(randgen, themefunc.H[0], themefunc.H[1]) let s = rand(randgen, themefunc.S[0], themefunc.S[1]) let l = rand(randgen, 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, '') }, stylize: stylize } // Utilities const 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 }, nickname: function (str) { if (str.match(/[a-z_\-\[\]\\^{}|`][a-z0-9_\-\[\]\\^{}|`]*/i)) { return true } return false }, escapeHTML: function (str) { return str.replace(//, '>') } } const processors = { inlineColor: function (text) { const rgbRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi const substitute = '$1$2
' text = text.replace(rgbRegex, substitute) return text }, linkify (text) { const re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:''.,<>?«»“”‘’]))/gi return text.replace(re, function (url) { let href = url if (url.indexOf('http') !== 0) { href = 'http://' + url } return '' + url + '' }) }, channelify (text) { if (!irc.config.channelify) return text const channelRegex = /(^|[\s,.:;?!"'()+@-\~%])(#+[^\x00\x07\r\n\s,:]*[a-z][^\x00\x07\r\n\s,:]*)/gmi const substitute = '$1$2' return text.replace(channelRegex, substitute) }, emojify (text) { if (irc.config.emoji === true && window.twemoji !== undefined) { // Emoji live in the D800-DFFF surrogate plane; only bother passing // this range to CPU-expensive parse() const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g if (emojiRegex.test(text)) { return twemoji.parse(text) } else { return text } } else { return text } } } function whoisMessage (whoisData, buffer) { let messages = [] for (let key in whoisData) { switch (key) { case 'hostmask': messages.push(span(whoisData[key], ['hostmask']) + ': ' + 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 = sf('is on %s', span(whoisData[key], ['server', 'nick'])) if (whoisData['serverName']) { adfd += ' ' adfd += span(validators.escapeHTML(whoisData['serverName']), ['hostmask']) } 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 = sf('[%s] %s', span(whoisData.nickname, ['nick']), messages[i]) buffer.addMessage(mesg, null, 'whois') } } const whoQueue = {} /* function queryChannelMembers (server, channel) { whoQueue[channel] = true sendToServer(server, { command: 'who', arguments: [channel] }) } */ function whoResponse (data, buffer) { let srv = irc.chat.getChatBuffersByServer(data.from) let channels = [] for (let i in srv) { let j = srv[i] if (j.type === 'channel' && j.name === data.target) { channels.push(j) } } if (channels.length > 0) { for (let c in channels) { let chan = channels[c] if (!chan.nicklist) continue for (let i in data.who) { let who = data.who[i] chan.nicklist.setModesFromString(who.modes, who.nickname) } } } // Send message to buffer if (whoQueue[data.target]) { delete whoQueue[data.target] return } let messages = [] for (let i in data.who) { let dt = data.who[i] messages.push( sf('[%s] %s %s %s %s (%s)', span(dt.channel, ['channel']), span(dt.nickname, ['nick']), span(dt.hostmask, ['hostmask']), dt.modes, dt.hopcount, validators.escapeHTML(dt.realname) ) ) } messages.push(sf('[%s] %s', span(data.target, [(data.target.indexOf('#') === 0 ? 'channel' : 'nick')]), data.message)) for (let i in messages) { buffer.addMessage(messages[i], null, 'whois') } } const asterisk = span('ℹ', ['asterisk']) const arrowout = span('⬅', ['arrowout']) const arrowin = span('➡', ['arrowin']) let composer = { message: { simple: function (time, sender, message, type, server) { let element = document.createElement('div') element.className = 'message type_simple m_' + type if (irc.config.timestamps) { element.innerHTML += span(formatDate(time, irc.config.timestampFormat), ['timestamp']) element.innerHTML += ' ' } if (irc.config.colors) { message = colorizer.stylize(message) } else { message = colorizer.strip(message) } message = processors.inlineColor(message) message = processors.channelify(message) message = processors.linkify(message) message = processors.emojify(message) switch (type) { case 'mode': element.innerHTML += ' ' + span(sender, ['actionee', 'nick']) + ' ' element.innerHTML += span(message, ['content']) break case 'action': element.innerHTML += asterisk + ' ' + span(sender, ['actionee', 'nick']) + ' ' element.innerHTML += span(message, ['content']) break case 'part': case 'quit': case 'kick': element.innerHTML += arrowout + ' ' + span(span(sender, ['actionee', 'nick']) + ' ' + message, ['content']) break case 'join': element.innerHTML += arrowin + ' ' + span(span(sender, ['actionee', 'nick']) + ' ' + message, ['content']) break case 'ctcpResponse': element.innerHTML += asterisk + ' CTCP response from ' + span(sender, ['actionee', 'nick']) + ' ' element.innerHTML += span(message, ['content']) break case 'ctcpRequest': element.innerHTML += asterisk + ' CTCP request to ' + span(sender, ['actionee', 'nick']) + ' ' element.innerHTML += span(message, ['content']) break default: if (sender) { element.innerHTML += span(sender, ['sender']) + ' ' + span(message, ['content']) } else { element.innerHTML += span(message, ['content', 'no_sender']) } break } if (irc.config.colorNicknames === true) { if (sender) { let sndr1 = element.querySelector('.sender') if (sndr1) { sndr1.style.color = colorizer.getRandomColor(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.getRandomColor(sndr2[a].innerHTML) } } } } return element } }, themeSelection: 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 finalChannels = [] 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.extras = 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] } finalChannels.push(tmp[i]) } } else { if (channels.indexOf('#') !== 0) { channels = '#' + channels } finalChannels.push(channels) } builder += finalChannels.join(',') } return builder } } function sendToServer (server, obj) { let i = irc.handler.getClientByActualServer(server) if (!i) throw new Error('Invalid connection ' + server) i.emit('userInput', obj) } // onclick food window.irc.joinChannel = (channel) => { let buf = irc.chat.getActiveChatBuffer() if (!buf || !buf.server) return sendToServer(buf.server, { command: 'join', message: '', arguments: [channel] }) return false } // 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.channels.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) { sendToServer(buffer.server, { command: 'join', message: '', arguments: [buffer.name] }) } else { handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') } } else { sendToServer(buffer.server, { command: 'join', message: '', arguments: [listargs[1]] }) } }, description: ' - Join a channel' }, part: { execute: function (buffer, handler, command, message, listargs) { if (!listargs[1] && buffer.type === 'channel') { sendToServer(buffer.server, { command: 'part', 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(' ') } sendToServer(buffer.server, { command: 'part', message: msg, arguments: [listargs[1]] }) } else { if (buffer.type === 'channel') { sendToServer(buffer.server, { command: 'part', 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') { sendToServer(buffer.server, { command: 'topic', 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(' ') } sendToServer(buffer.server, { command: 'topic', message: msg, arguments: [listargs[1]] }) } else { if (buffer.type === 'channel') { sendToServer(buffer.server, { command: 'topic', 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 !') } sendToServer(buffer.server, { command: 'kick', 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) { sendToServer(buffer.server, { command: 'quit', message: listargs.slice(1).join(' '), arguments: [] }) }, description: '[] - Quit the current server with message.', aliases: ['exit'] }, mode: { execute: function (buffer, handler, command, message, listargs) { sendToServer(buffer.server, { command: 'mode', 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 } sendToServer(buffer.server, { command: 'privmsg', 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() sendToServer(buffer.server, { command: 'ctcp', 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 } sendToServer(buffer.server, { command: 'notice', message: listargs.slice(2).join(' '), arguments: [listargs[1]] }) }, description: ' - Sends a NOTICE to target.' }, action: { execute: function (buffer, handler, command, message, listargs) { sendToServer(buffer.server, { command: 'privmsg', message: '\x01ACTION ' + message.substring(command.length + 2) + '\x01', arguments: [buffer.name] }) }, description: ' - Act as yourself. (CTCP ACTION)', aliases: ['me'] }, list: { execute: function (buffer, handler, command, message, listargs) { sendToServer(buffer.server, { command: 'list', 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 !== '' && irc.serverData[buffer.server] != null) { let mynick = irc.serverData[buffer.server].userNick buffer.addMessage('Your nickname is: ' + mynick + '', null, 'help') } return } sendToServer(buffer.server, { command: 'nick', message: '', arguments: listargs.splice(1) }) }, description: '[] - Set/display your nickname.', 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, '/' + command.toUpperCase() + ': Buffer is not a channel!') } } else if (listargs[1].indexOf('#') === 0) { channel = listargs[1] } else { return handler.commandError(buffer, '/' + command.toUpperCase() + ': Invalid channel name!') } sendToServer(buffer.server, { command: 'names', message: '', arguments: [channel] }) }, description: '- List all users on the current channel.', aliases: ['nicknames'] }, quote: { execute: function (buffer, handler, command, message, listargs) { sendToServer(buffer.server, { command: listargs[1], 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!') sendToServer(buffer.server, { command: 'whois', message: '', arguments: [listargs[1]] }) }, description: ' - Display information about a user.' }, who: { execute: function (buffer, handler, command, message, listargs) { if (!listargs[1]) listargs[1] = buffer.name sendToServer(buffer.server, { command: 'who', arguments: [listargs[1]] }) }, description: '[] - Display some information.' }, 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.' }, ping: { execute: function (buffer, handler, command, message, listargs) { let server = irc.handler.getClientByActualServer(buffer.server) buffer.addMessage('Connection latency: ' + server.ping + 'ms', null, 'help') }, aliases: ['latency', 'lag'] }, 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(span('/' + cmd.toUpperCase(), ['command']) + ' ' + validators.escapeHTML(commands[cmd].description), null, 'help') } else { buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' - 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(span('/' + cmd.toUpperCase(), ['command']) + ' ' + validators.escapeHTML(commands[foundAliased].description), null, 'help') } else { buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' - No description provided', null, 'help') } } else { handler.commandError(buffer, span('/' + cmd.toUpperCase(), ['command']) + ': 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 += span(nick.prefix, ['prefix']) } else { construct += span(' ', ['no-prefix']) } if (irc.config.colorNicklist) { construct += '' + nick.nickname + '' } else { construct += span(nick.nickname, ['nickname']) } str.innerHTML = construct clientdom.nicklist.appendChild(str) } reset () { this.nicks = [] this.simplifiedNicksList = [] } render () { if (!this.buffer.active) return if (!irc.serverData[this.buffer.server]) 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.inputHandler.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() removeStr(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 } highestPriorityMode (modes) { let highest = null let modeTranslations = irc.serverData[this.buffer.server].modeTranslation for (let mode in modeTranslations) { if (modes.indexOf(mode) !== -1) { highest = mode break } } return { highest, prefix: highest ? modeTranslations[highest] : null } } modeAdded (nickname, newMode) { let nickIndex = this.getNickIndex(nickname) let nick = null if (nickIndex != null) { nick = this.nicks[nickIndex] } else { return } // Add mode to nicks' modes list nick.modes.push(newMode) // Find the prefix to give to the nick nick.prefix = this.highestPriorityMode(nick.modes).prefix || '' this.render() } modeRemoved (nickname, oldMode) { let nickIndex = this.getNickIndex(nickname) let nick = null if (nickIndex != null) { nick = this.nicks[nickIndex] } else { return } // Remove mode from nicks' modes list removeStr(nick.modes, oldMode) // Find the prefix to give to the nick nick.prefix = this.highestPriorityMode(nick.modes).prefix || '' this.render() } setModesFromString (str, nickname) { if (this.getNickIndex(nickname) === null) return let spfx = irc.serverData[this.buffer.server].modeTranslation let modes = [] let split = str.split('') for (let i in split) { let char = split[i] if (char === 'H' || char === 'G' || char === '*' || char === 's') continue for (let mode in spfx) { let prefix = spfx[mode] if (char === prefix) { modes.push(mode) break } } } let nick = this.nicks[this.getNickIndex(nickname)] nick.modes = modes nick.prefix = this.highestPriorityMode(nick.modes).prefix || '' this.render() } } class Tab { constructor (buffer) { this.buffer = buffer this.element = null this.closeRequested = false } // Create a tab element renderTab () { let internals = span(this.buffer.title, null, 'title') + span('', ['none'], 'unread') 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 += span('x', null, 'close') 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 = processors.channelify(processors.linkify(colorizer.stylize(this.topic))) } else { clientdom.topicbar.innerHTML = processors.channelify(processors.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] ? irc.serverData[this.server].userNick : el('i', 'Disconnected') 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, this.server) if (irc.serverData[this.server]) { let mynick = irc.serverData[this.server].userNick 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 = processors.linkify(colorizer.stylize(topic)) } else { clientdom.topicbar.innerHTML = processors.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].userNick 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.inputHandler.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(el('i', sf('(%s)', this.title))) } } closeChatBuffer () { irc.chat.closeChatBuffer(this) } } class ThemeSelector { constructor (settings, variable) { this.settings = settings this.variable = variable } setActiveSelection (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.setActiveSelection(theme) } } render () { clientdom.settings.available_themes.innerHTML = '' for (let n in themes.available) { let theme = themes.available[n] let button = composer.themeSelection(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 } 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() } } switchTheme () { if (this.themeSelection !== '') { themes.changeTheme(this.themeSelection) irc.config.theme = this.themeSelection this.themeSelector.setActiveSelection(this.themeSelection) } } saveSpecified () { if (this.timeout) clearTimeout(this.timeout) this.switchTheme() 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 = span('Settings saved!', ['success']) 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 ConnectionHandler { constructor () { this.clients = {} } getClientByActualServer (actual) { for (let i in this.clients) { let a = this.clients[i] if (a.data.actualServer === actual) return a } return null } createConnection (data) { let client = new IRCConnection(data, gcfg, { ctcps: customCTCPs }) irc.auther.authMessage('Connecting to server...', false) client.on('authenticated', () => { irc.auther.authComplete() this.clients[data.server] = client this.bindAll(data.server, client) irc.chat.newServerChatBuffer(client) irc.chat.joinChannels(client.data.actualServer, data.channels) }) client.once('connectionError', (e) => { if (e.message.indexOf('IRCError') !== 0) return irc.auther.authMessage(e.message, true) }) client.connect().catch(e => { console.error(e) irc.auther.authMessage('Socket connection failed!', true) }) } bindAll (srv, client) { let server = client.data.actualServer client.on('fromServer', (data) => { switch (data.type) { case 'joinChannel': if (data.user.nickname === irc.serverData[server].userNick) { irc.chat.createChatBuffer(server, data.channel, 'channel', true) } irc.chat.handleJoin(server, data.user, data.channel) break case 'kickedFromChannel': irc.chat.handleLeave(server, data.kickee, data.channel, data.reason, data.user) break case 'partChannel': irc.chat.handleLeave(server, data.user, data.channel, data.reason) break case 'quit': irc.chat.handleQuit(server, data.user, data.reason) break case 'message': if (data.to === irc.serverData[server].userNick) { irc.chat.messageChatBuffer(data.user.nickname, server, { message: data.message, type: data.messageType, from: data.user.nickname }) } else if (data.to == null) { let atest = irc.chat.getActiveChatBuffer() if (atest.server !== server) { atest = irc.chat.getServerChatBuffer(server) } atest.addMessage(data.message, data.user.nickname, data.messageType) } else { irc.chat.messageChatBuffer(data.to, server, { message: data.message, type: data.messageType, from: data.user.nickname }) } break case 'nicks': irc.chat.buildNicklist(data.channel, server, data.nicks) break case 'topic': if (data['topic'] && data['setBy']) { irc.chat.topicChange(data.channel, server, data.topic, data['setBy']) } else if (data['topic']) { irc.chat.topicChange(data.channel, server, data.topic, null) } else if (data['setBy']) { irc.chat.messageChatBuffer(data.channel, server, { message: 'Topic set by ' + data.setBy + ' on ' + (new Date(data.time * 1000)), type: 'topic', from: null }) } break case 'nick': irc.chat.nickChange(server, data.nick, data.newNick) break case 'modeAdd': case 'modeDel': case 'mode': irc.chat.handleMode(server, data) break case 'serverMessage': if (data['error']) data.messageType = 'error' if (irc.chat.getServerChatBuffer(server) == null) { if (!irc.serverChatQueue[server]) { irc.serverChatQueue[server] = [] } else { irc.serverChatQueue[server].push({ type: data.messageType, message: data.message, from: data.from || null, time: new Date() }) } } else { irc.chat.messageChatBuffer(server, server, { message: data.message, type: data.messageType, from: data.from || null }) } break case 'connectMessage': irc.auther.authMessage(data.message, data.error) break case 'whoisResponse': whoisMessage(data.whois, irc.chat.getActiveChatBuffer()) break case 'whoResponse': whoResponse(data, irc.chat.getActiveChatBuffer()) break case 'listedChannel': irc.chat.messageChatBuffer(server, server, { message: span(data.channel, ['channel']) + ' ' + span(data.users, ['usercount']) + ' ' + span(data.topic, ['topic']), type: 'listentry', from: data.from }) break } }) client.on('connectionClosed', () => { let serverz = irc.chat.getChatBuffersByServer(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[server]) { delete irc.serverData[server] } stopWarnings() this.clients[srv] = null }) } } class IRCConnector { constructor () { this.formLocked = false this.canClose = false this.defaults = {} clientdom.connector.form.onsubmit = (e) => { if (this.formLocked) { e.preventDefault() return false } this.formLocked = true this.validateForm(e) } clientdom.connector.onkeyup = (e) => { let key = e.keyCode || e.which || e.charCode || 0 if (key === 27 && this.canClose) { this.authComplete() } } clientdom.connector.pwtrigger.onclick = (e) => { this.togglePassword() } } fillFormFromURI (urlParams) { let skipServer = false if (this.defaults.server) { clientdom.connector.server.value = this.defaults.server } if (this.defaults.port) { clientdom.connector.port.value = this.defaults.port } if (this.defaults.ssl) { clientdom.connector.secure.checked = this.defaults.ssl } 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(seedrandom(1), 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)) { skipServer = true clientdom.connector.server.value = value } break case 'serverinfo': 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 && !skipServer) { 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 } } } defaultTo (data) { this.defaults = data } 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, channels: channel, server: server, port: port, password: password, secure: clientdom.connector.secure.checked } } validateForm (event) { event.preventDefault() let data = this.getDataFromForm() if (!data) return irc.handler.createConnection(data) return true } authMessage (message, error) { clientdom.connector.messenger.innerHTML = span(message, ['msg', (error ? 'error' : '')]) 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 = (e) => { let key = e.keyCode || e.which || e.charCode || 0 if (key === 13) { this.handleInput() return } this.keyUpHandle(e, 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 } } 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 { sendToServer(buffer.server, { command: 'privmsg', 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.inputHandler = 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 break } } return result } getActiveChatBuffer () { let result = null for (let t in this.buffers) { let buf = this.buffers[t] if (buf.active === true) { result = buf break } } return result } getServerChatBuffer (server) { let result = null for (let t in this.buffers) { let buf = this.buffers[t] if (buf.server === server) { result = buf break } } 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 break } } 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 'You will be disconnected from all the channels you\'re currently in.' } } let prefixes = '' for (let v in serverinfo.data.supportedModes) { prefixes += serverinfo.data.supportedModes[v] } irc.serverData[serverinfo.data.actualServer] = { modeTranslation: serverinfo.data.supportedModes, supportedPrefixes: prefixes, network: serverinfo.data.network, userNick: serverinfo.config.nickname, maxChannelLength: serverinfo.data.maxChannelLength } let newServer = new ChatBuffer(serverinfo.data.actualServer, serverinfo.data.actualServer, serverinfo.data.network, 'server') this.buffers.push(newServer) this.render(newServer) this.firstServer = false if (irc.serverChatQueue[serverinfo.data.actualServer]) { for (let a in irc.serverChatQueue[serverinfo.data.actualServer]) { let mesg = irc.serverChatQueue[serverinfo.data.actualServer][a] newServer.addMessage(mesg.message, mesg.from, mesg.type, mesg.time) } delete irc.serverChatQueue[serverinfo.data.actualServer] } } 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') { sendToServer(buffer.server, { command: 'quit', message: 'Server tab closed', arguments: [] }) } else if (buffer.type === 'channel' && buffer.alive) { sendToServer(buffer.server, { command: 'part', 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.message = message.message.substring(0, message.message.length - 1) 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 = [] buf.nicklist.reset() 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(span(nick.prefix, ['prefix']) + span(nick.nickname, ['nick'])) } else { nick.nickname = nicks[n] channelSendNicks.push(span(nick.nickname, ['nick'])) } buf.nicklist.nickAddObject(nick) } buf.addMessage(sf('Nicks %s: %s', span(channel, ['channel']), channelSendNicks.join(', ')), null, 'names') if (buf.active) { buf.nicklist.render() } } nickChange (server, oldNick, newNick) { let buffers = this.getChatBuffersByServer(server) if (irc.serverData[server].userNick === oldNick) { irc.serverData[server].userNick = 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(sf('%s is now known as %s', 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(sf('%s has changed the topic of %s to %s', span(changer, ['nick']), channel, topic), null, 'topic') } else { buf.addMessage(sf('Topic of %s is \'%s\'', span(channel, ['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(sf('%s has quit %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), reason), user.nickname, 'quit') } } handleJoin (server, user, channel) { let buffer = this.getChatBufferByServerName(server, channel) if (!buffer) return if (user.nickname === irc.serverData[server].userNick) { buffer.setAliveStatus(true) } else { buffer.nicklist.nickAdd(user.nickname) } buffer.addMessage(sf('%s has joined %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), channel), user.nickname, 'join') } handleLeave (server, user, channel, reason, kicker) { let buffer = this.getChatBufferByServerName(server, channel) if (!buffer) return if (user['nickname']) { if (irc.serverData[server] && user.nickname === irc.serverData[server].userNick) { buffer.setAliveStatus(false) } } else { if (irc.serverData[server] && user === irc.serverData[server].userNick) { buffer.setAliveStatus(false) } } if (kicker) { buffer.addMessage(sf('has kicked %s %s', span(user, ['nick']), reason), kicker.nickname, 'part') } else { buffer.addMessage(sf('%s has left %s %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), channel, (reason != null ? span(reason, ['reason']) : '')), user.nickname, 'part') } if (kicker) { buffer.nicklist.nickRemove(user) } else { buffer.nicklist.nickRemove(user.nickname) } } handleMode (server, data) { let buf = null if (data.target === irc.serverData[server].userNick) { buf = this.getServerChatBuffer(server) } else { buf = this.getChatBufferByServerName(server, data.target) } if (!buf) return if (data.type === 'modeAdd') { buf.nicklist.modeAdded(data.modeTarget, data.mode) buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(sf('+%s %s', data.mode, data.modeTarget), ['mode']), data.user.nickname, 'mode') } else if (data.type === 'modeDel') { buf.nicklist.modeRemoved(data.modeTarget, data.mode) buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(sf('-%s %s', data.mode, data.modeTarget), ['mode']), data.user.nickname, 'mode') } else { buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(data.message, ['mode']), data.user.nickname, 'mode') } } joinChannels (server, channel) { if (typeof channel !== 'object') { 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 = [] } } sendToServer(server, { command: 'join', message: '', arguments: channel }) } changeTitle (title) { // TODO: notify of hot buffers document.title = title irc.documentTitle = title } render (buffer) { let activeNow = this.getActiveChatBuffer() this.inputHandler.tabCompleteReset() if (activeNow) { activeNow.switchOff() } buffer.render() } } // Initialization function parseURL () { let match let pl = /\+/g // Regex for replacing addition symbol with a space let search = /([^&=]+)=?([^&]*)/g let decode = (s) => { return decodeURIComponent(s.replace(pl, ' ')) } let query = window.location.search.substring(1) let urlParams = {} while ((match = search.exec(query)) != null) { urlParams[decode(match[1])] = decode(match[2]) } irc.auther.fillFormFromURI(urlParams) } function stopWarnings () { if (Object.keys(irc.serverData).length === 0) { window.onbeforeunload = null } } 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.settings = new Settings() irc.settings.setInitialValues() irc.handler = new ConnectionHandler() irc.auther = new IRCConnector() irc.chat = new IRCChatWindow() parseURL() window.onpopstate = parseURL }