This repository has been archived on 2022-11-26. You can view files and clone it, but cannot push or open issues or pull requests.
teemant-old/src/js/main.js

2278 lines
65 KiB
JavaScript

/* 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(/</g, '&lt;').replace(/>/, '&gt;')
}
}
const processors = {
inlineColor: function (text) {
const rgbRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi
const substitute = '$1$2 <div class="color_sample" style="background-color:$2"></div>'
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 '<a href="' + href + '" target="_blank" rel="nofollow">' + url + '</a>'
})
},
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<a onclick="irc.joinChannel(\'$2\');" class="channel">$2</a>'
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 += '&nbsp;'
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]&nbsp;%s', span(whoisData.nickname, ['nick']), messages[i])
buffer.addMessage(mesg, null, 'whois')
}
}
const asterisk = span('&#8505;', ['asterisk'])
const arrowout = span('&#11013;', ['arrowout'])
const arrowin = span('&#10145;', ['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 += '&nbsp;'
}
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 += '&nbsp;' + span(sender, ['actionee', 'nick']) + '&nbsp;'
element.innerHTML += span(message, ['content'])
break
case 'action':
element.innerHTML += asterisk + '&nbsp;' + span(sender, ['actionee', 'nick']) + '&nbsp;'
element.innerHTML += span(message, ['content'])
break
case 'part':
case 'quit':
case 'kick':
element.innerHTML += arrowout + '&nbsp;' + span(span(sender, ['actionee', 'nick']) + '&nbsp;' + message, ['content'])
break
case 'join':
element.innerHTML += arrowin + '&nbsp;' + span(span(sender, ['actionee', 'nick']) + '&nbsp;' + message, ['content'])
break
case 'ctcpResponse':
element.innerHTML += asterisk + '&nbsp;CTCP response from ' + span(sender, ['actionee', 'nick']) + '&nbsp;'
element.innerHTML += span(message, ['content'])
break
case 'ctcpRequest':
element.innerHTML += asterisk + '&nbsp;CTCP request to ' + span(sender, ['actionee', 'nick']) + '&nbsp;'
element.innerHTML += span(message, ['content'])
break
default:
if (sender) {
element.innerHTML += span(sender, ['sender']) + '&nbsp;' + 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.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) {
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: '<channel> - 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>] [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>] [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 <user>!')
}
sendToServer(buffer.server, {
command: 'kick',
message: listargs.slice(2).join(' '),
arguments: [buffer.name, listargs[1]]
})
},
description: '<user> <message> - 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: '[<message>] - 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: '<target> <message> - 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: '<target> <type> [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: '<target> <message> - 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: '<message> - 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: <span class="nick">' + mynick + '</span>', null, 'help')
}
return
}
sendToServer(buffer.server, { command: 'nick', message: '', arguments: listargs.splice(1) })
},
description: '[<nickname>] - 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: '<command> [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: '<nickname> - 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.'
},
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']) + '&nbsp;' +
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']) + '&nbsp;' +
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: '<command> - 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('&nbsp;', ['no-prefix'])
}
if (irc.config.colorNicklist) {
construct += '<span class="nickname" style="color: ' + colorizer.getRandomColor(nick.nickname) + ';">' + nick.nickname + '</span>'
} else {
construct += span(nick.nickname, ['nickname'])
}
str.innerHTML = construct
clientdom.nicklist.appendChild(str)
}
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
}
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
removeStr(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 = 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.linkify(processors.channelify(colorizer.stylize(this.topic)))
} else {
clientdom.topicbar.innerHTML = processors.linkify(processors.channelify(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.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 'listedChannel':
irc.chat.messageChatBuffer(server, server,
{
message: span(data.channel, ['channel']) + '&nbsp;' +
span(data.users, ['usercount']) + '&nbsp;' +
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) {
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)) {
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) {
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 = []
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('.userNickname')
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
}