it doesnt build yet

This commit is contained in:
Evert Prants 2019-01-09 19:26:09 +02:00
parent 73610f6c18
commit 6e39b056c3
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
21 changed files with 9469 additions and 1281 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.js]
charset = utf-8
indent_style = space
indent_size = 2

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
/node_modules/
/build/
/dist/
/lib/
/client.config.toml
/webirc.data.json

View File

@ -1,15 +1,15 @@
# TeemantIRC
LunaSquee's third attempt at creating a working IRC client..
###Running the development version
### Running the development version
This application requires [node.js](https://nodejs.org/) to be installed.
1. Install the dependencies `npm install`
2. Copy the configuration `cp client.config.example.toml client.config.toml`
3. Build the project using `gulp`
3. Build the project using `npm run build`
4. Run the server `./teemant.js`
The client will be accessible at http://localhost:8080/
The client will be accessible at http://localhost:9000/
### WebIRC

View File

@ -1,279 +0,0 @@
'use strict';
const path = require('path');
const gulp = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const gutil = require('gulp-util');
const gdata = require('gulp-data');
const del = require('del');
const gulpif = require('gulp-if');
const plumber = require('gulp-plumber');
const mergeStream = require('merge-stream');
const lodash = require('lodash');
const Sequence = require('run-sequence');
const watch = require('gulp-watch');
const realFavicon = require('gulp-real-favicon');
const debug = require('gulp-debug');
const filter = require('gulp-filter');
const fs = require('fs');
const eslint = require('gulp-eslint');
const webpack = require('webpack');
const webpackConfig = require(path.join(__dirname, '/webpack.config'));
const stylus = require('gulp-stylus');
const nib = require('nib');
const csso = require('gulp-csso');
const htmlmin = require('gulp-htmlmin');
const sequence = Sequence.use(gulp);
let sources = {
// script: ['main.js', 'admin.coffee', 'popout.js', 'livestream.js'],
style: ['layout.styl', 'theme_default.styl', 'theme_night.styl'],
document: ['index.html']
};
let lintES = ['src/script/**/*.js', 'server/**/*.js', 'gulpfile.js', 'teemant.js', 'webpack.config.js'];
let inProduction = process.env.NODE_ENV === 'production' || process.argv.indexOf('-p') !== -1;
let eslintOpts = {
rules: {
quotes: [1, 'single'],
semi: [1, 'always']
},
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
globalReturn: true
}
}
};
let stylusOpts = {
use: nib(),
compress: false
};
let cssoOpts = {
restructure: true
};
let htmlminOpts = {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true,
collapseBooleanAttributes: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
};
let watchOpts = {
readDelay: 500,
verbose: true
};
// File where the favicon markups are stored
let faviconDataFile = 'build/icons/favicon-data.json';
if (inProduction) {
webpackConfig.plugins.push(new webpack.optimize.DedupePlugin());
webpackConfig.plugins.push(new webpack.optimize.OccurenceOrderPlugin(false));
webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true
},
comments: false,
mangle: {
screw_ie8: true
},
screw_ie8: true,
sourceMap: false
}));
}
let wpCompiler = webpack(lodash.assign({}, webpackConfig, {
cache: {},
devtool: inProduction ? null : 'inline-source-map',
debug: !inProduction
}));
function webpackTask (callback) {
// run webpack
wpCompiler.run(function (err, stats) {
if (err) throw new gutil.PluginError('webpack', err);
gutil.log('[script]', stats.toString({
colors: true,
hash: false,
version: false,
chunks: false,
chunkModules: false
}));
if (typeof callback === 'function') callback();
});
}
function styleTask () {
return gulp.src(sources.style.map(function (f) { return 'src/style/' + f; }))
.pipe(plumber())
.pipe(gulpif(!inProduction, sourcemaps.init()))
.pipe(stylus(stylusOpts))
.pipe(gulpif(inProduction, csso(cssoOpts)))
.pipe(gulpif(!inProduction, sourcemaps.write()))
.pipe(debug({title: '[style]'}))
.pipe(gulp.dest('build/style/'));
}
function documentTask (p) {
let data = {
config: require('./server/config'),
env: process.env.NODE_ENV || 'development',
};
return p
.pipe(plumber())
.pipe(gdata(function () { return data; }))
.pipe(realFavicon.injectFaviconMarkups(JSON.parse(fs.readFileSync(faviconDataFile)).favicon.html_code))
.pipe(gulpif(inProduction, htmlmin(htmlminOpts)))
.pipe(gulp.dest('build/document/'))
.pipe(debug({title: '[document]'}));
}
// Cleanup tasks
gulp.task('clean', () => del('build'));
gulp.task('clean:quick', ['clean:script', 'clean:style'], (done) => {
done();
});
gulp.task('clean:script', () => {
return del('build/script');
});
gulp.task('clean:style', () => {
return del('build/style');
});
gulp.task('clean:icons', () => {
return del('build/icons');
});
gulp.task('clean:document', () => {
return del('build/document');
});
// Main tasks
gulp.task('script', ['clean:script'], webpackTask);
gulp.task('watch:script', () => {
return watch(['src/script/**/*.js'], watchOpts, webpackTask);
});
gulp.task('style', ['clean:style'], styleTask);
gulp.task('watch:style', () => {
return watch('src/style/**/*.styl', watchOpts, styleTask);
});
// Generate the icons. This task takes a few seconds to complete.
// You should run it at least once to create the icons. Then,
// you should run it whenever RealFaviconGenerator updates its
// package (see the update-favicon task below).
gulp.task('generate-favicon', ['clean:icons'], (done) => {
realFavicon.generateFavicon({
masterPicture: 'static/image/diamond.svg',
dest: 'build/icons/',
iconsPath: '/',
design: {
ios: {
masterPicture: 'static/image/diamond.svg',
pictureAspect: 'backgroundAndMargin',
backgroundColor: '#00c7e0',
margin: '0%',
appName: 'Teemant'
},
desktopBrowser: {},
windows: {
pictureAspect: 'noChange',
backgroundColor: '#00c7e0',
onConflict: 'override',
appName: 'Teemant'
},
androidChrome: {
masterPicture: 'static/image/diamond.svg',
pictureAspect: 'noChange',
themeColor: '#00c7e0',
manifest: {
name: 'Teemant',
display: 'standalone',
orientation: 'notSet',
onConflict: 'override',
declared: true
}
},
safariPinnedTab: {
pictureAspect: 'silhouette',
themeColor: '#00c7e0'
}
},
settings: {
scalingAlgorithm: 'Lanczos',
errorOnImageTooSmall: false
},
versioning: true,
markupFile: faviconDataFile
}, done);
});
gulp.task('update-favicon', (done) => {
let currentVersion;
try {
currentVersion = JSON.parse(fs.readFileSync(faviconDataFile)).version;
} catch (e) {}
if (currentVersion) {
realFavicon.checkForUpdates(currentVersion, function (err) {
if (err) {
throw err;
}
done();
});
} else {
sequence('generate-favicon', done);
}
});
gulp.task('document', ['clean:document', 'update-favicon'], () => {
return documentTask(gulp.src(sources.document.map(function (f) { return 'src/document/' + f; })));
});
gulp.task('watch:document', () => {
return documentTask(
watch(['src/document/**/*.html'], watchOpts)
.pipe(filter(sources.document.map(function (f) { return 'src/document/' + f; })))
);
});
gulp.task('lint', () => {
return mergeStream(
gulp.src(lintES).pipe(eslint(eslintOpts))
.pipe(eslint.format())
);
});
gulp.task('watch:lint', () => {
return mergeStream(
watch(lintES, watchOpts, function (file) {
gulp.src(file.path).pipe(eslint(eslintOpts))
.pipe(eslint.format());
})
);
});
// Default task
gulp.task('default', (done) => {
sequence('script', 'style', 'lint', 'document', done);
});
// Watch task
gulp.task('watch', (done) => {
sequence('default', ['watch:lint', 'watch:script', 'watch:style', 'watch:document'], done);
});

8290
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "teemantirc",
"version": "1.1.0",
"version": "2.0.0",
"description": "Web-based IRC client",
"main": "teemant.js",
"scripts": {
@ -12,38 +12,26 @@
"author": "Evert",
"license": "MIT",
"dependencies": {
"express": "^4.14.0",
"socketio": "^1.0.0",
"toml": "^2.3.0"
},
"devDependencies": {
"gulp": "^3.9.1",
"lodash": "^4.17.2",
"webpack": "^1.13.3",
"del": "^2.2.2",
"gulp-csso": "^2.0.0",
"gulp-data": "^1.2.1",
"gulp-debug": "^3.0.0",
"gulp-eslint": "^3.0.1",
"gulp-filter": "^4.0.0",
"gulp-htmlmin": "^3.0.0",
"gulp-if": "^2.0.2",
"gulp-plumber": "^1.1.0",
"gulp-pug": "^3.2.0",
"gulp-real-favicon": "^0.2.1",
"gulp-sourcemaps": "^1.9.1",
"gulp-standard": "^8.0.2",
"gulp-stylus": "^2.6.0",
"gulp-util": "^3.0.7",
"gulp-watch": "^4.3.11",
"gulp-watch-pug": "^1.0.0",
"lazypipe": "^1.0.1",
"merge-stream": "^1.0.1",
"nib": "^1.1.2",
"run-sequence": "^1.2.2"
"express": "^4.16.4",
"socket.io": "^2.2.0",
"toml": "^2.3.5"
},
"repository": {
"type": "git",
"url": "git://github.com/DiamondtechDev/TeemantIRC.git"
"url": "git://gitlab.icynet.eu/IcyNetwork/teemant.git"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.2.3",
"babel-loader": "^8.0.5",
"css-loader": "^2.1.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"standard": "^12.0.1",
"stylus-loader": "^3.0.2",
"webpack": "^4.28.1",
"webpack-command": "^0.4.2"
}
}

View File

@ -1,14 +1,15 @@
let fs = require('fs');
let toml = require('toml');
let filename = __dirname+'/../client.config.toml';
import fs from 'fs'
import path from 'path'
import toml from 'toml'
let config;
const filename = path.join(__dirname, '..', 'client.config.toml')
let config
try {
config = toml.parse(fs.readFileSync(filename));
config = toml.parse(fs.readFileSync(filename))
} catch (e) {
throw 'config.toml parse error: ' + e;
console.error(e.stack);
throw new Error('config.toml parse error: ', e.message)
}
module.exports = config;
module.exports = config

7
server/irc/index.js Normal file
View File

@ -0,0 +1,7 @@
import connector from './irc.js'
import parser from './parser.js'
module.exports = {
IRCConnection: connector,
Parser: parser
}

713
server/irc/irc.js Normal file
View File

@ -0,0 +1,713 @@
import { EventEmitter } from 'events'
import net from 'net'
import tls from 'tls'
import util from 'util'
import parse from './parser'
class IRCConnectionHandler {
constructor (connection) {
this.conn = connection
}
handleUserLine (data) {
switch (data.command) {
case 'topic':
this.conn.write('%s %s', data.command.toUpperCase(), data.arguments[0], (data.message !== '' ? ' :' + data.message : ''))
break
case 'kick':
this.conn.write('%s %s :%s', data.command.toUpperCase(), data.arguments.join(' '), data.message)
break
case 'part':
this.conn.write('%s %s :%s', data.command.toUpperCase(), data.arguments[0], data.message)
break
case 'nick':
case 'whois':
case 'who':
case 'names':
case 'join':
this.conn.write('%s %s', data.command.toUpperCase(), data.arguments[0])
break
case 'quit':
this.conn.write('%s :%s', data.command.toUpperCase(), (data.message === ''
? this.conn.globalConfig.default_quit_msg : data.message))
break
case 'privmsg':
this.conn.write('PRIVMSG %s :%s', data.arguments[0], data.message)
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'privmsg',
to: data.arguments[0],
user: {
nickname: this.conn.config.nickname
},
message: data.message,
server: data.server
})
break
case 'notice':
this.conn.write('NOTICE %s :%s', data.arguments[0], data.message)
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'notice',
to: data.arguments[0],
user: {
nickname: this.conn.config.nickname
},
message: data.message,
server: data.server
})
break
case 'list':
this.conn.write(data.command.toUpperCase())
break
case 'ctcp':
let ctcpmsg = ''
if (data.arguments[1].toLowerCase() === 'ping') {
ctcpmsg = 'PING ' + Math.floor(Date.now() / 1000)
} else {
ctcpmsg = data.message
}
this.conn.write('PRIVMSG %s :\x01%s\x01', data.arguments[0], ctcpmsg)
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'ctcp_request',
to: this.conn.config.nickname,
user: {
nickname: data.arguments[0]
},
message: ctcpmsg,
server: data.server
})
break
default:
this.conn.write(data.command.toUpperCase(), data.message)
}
if (data.targetType === 'channel' || data.targetType === 'message') {
this.conn.write('PRIVMSG %s :%s', data.target, data.message)
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'privmsg',
to: data.target,
user: {
nickname: this.conn.config.nickname
},
message: data.message,
server: data.server
})
}
}
whoisManage (whom, list) {
if (!this.conn.queue.whois) {
this.conn.queue.whois = {}
}
if (!this.conn.queue.whois[whom]) {
this.conn.queue.whois[whom] = list
} else {
for (let a in list) {
this.conn.queue.whois[whom][a] = list[a]
}
}
}
ctcpManage (data) {
let line = data.trailing.replace(/\x01/g, '').trim().split(' ') /* ignore no-control-regex */
if (!line[0]) return
line[0] = line[0].toUpperCase()
let resp = '\x01' + line[0] + ' %s\x01'
if (line[0] === 'PING' && line[1] != null && line[1] !== '') {
resp = util.format(resp, line.slice(1).join(' '))
} else if (line[0] === 'CLIENTINFO') {
resp = util.format(resp, 'CLIENTINFO PING ' + Object.keys(this.conn.extras.ctcps).join(' '))
} else if (this.conn.extras.ctcps && this.conn.extras.ctcps[line[0]] != null) {
resp = util.format(resp, this.conn.extras.ctcps[line[0]](data, this.conn))
} else {
resp = null
}
if (resp != null) {
this.conn.write('NOTICE %s :%s', data.user.nickname, resp)
}
return resp != null
}
handleServerLine (line) {
if (this.conn.queue['supportsmsg'] && line.command !== '005') {
delete this.conn.queue['supportsmsg']
if (this.conn.config.autojoin.length > 0) {
for (let t in this.conn.config.autojoin) {
this.conn.write('JOIN', this.conn.config.autojoin[t])
}
}
this.conn.emit('authenticated', {})
}
let serverName = this.conn.config.server
let realServerName = this.conn.data.actualServer
if (line.user.nickname === '') {
realServerName = line.user.hostname
}
let list = null
switch (line.command) {
case 'error':
this.conn.emit('connerror', { type: 'irc_error', raw: line.raw })
break
case '001':
this.conn.data.actualServer = line.user.hostname
break
case '005':
if (!this.conn.queue['supportsmsg']) {
this.conn.queue['supportsmsg'] = true
}
this.conn.authenticated = true
let argv = line.arguments.slice(1)
for (let a in argv) {
let t = argv[a]
if (t.indexOf('=') !== -1) {
t = t.split('=')
if (t[0] === 'PREFIX') {
let d = t[1].match(/\((\w+)\)(.*)/)
let r = d[1].split('')
let aa = d[2].split('')
for (let b in r) {
this.conn.data.supportedModes[r[b]] = aa[b]
}
} else if (t[0] === 'NETWORK') {
this.conn.data.network = t[1]
} else if (t[0] === 'CHANNELLEN') {
this.conn.data.maxChannelLength = parseInt(t[1])
}
this.conn.data.serverSupports[t[0]] = t[1]
} else {
this.conn.data.serverSupports[t] = true
}
}
break
case 'JOIN':
if (line.trailing) {
this.conn.emit('pass_to_client', {
type: 'event_join_channel',
user: line.user,
channel: line.trailing,
server: serverName
})
} else {
for (let i in line.arguments) {
this.conn.emit('pass_to_client', {
type: 'event_join_channel',
user: line.user,
channel: line.arguments[i],
server: serverName
})
}
}
break
case 'PART':
this.conn.emit('pass_to_client', {
type: 'event_part_channel',
user: line.user,
channel: line.arguments[0],
reason: line.trailing,
server: serverName
})
break
case 'QUIT':
this.conn.emit('pass_to_client', {
type: 'event_quit',
user: line.user,
reason: line.trailing,
server: serverName
})
break
case '353':
if (!this.conn.queue['names']) {
this.conn.queue['names'] = {}
}
let splittrail = line.trailing.split(' ')
for (let a in splittrail) {
let nick = splittrail[a]
if (nick.trim() === '') continue
if (this.conn.queue['names'][line.arguments[2]]) {
this.conn.queue['names'][line.arguments[2]].push(nick)
} else {
this.conn.queue['names'][line.arguments[2]] = [nick]
}
}
break
case '366':
if (!this.conn.queue['names']) break
if (this.conn.queue['names'][line.arguments[1]]) {
this.conn.emit('pass_to_client', {
type: 'channel_nicks',
channel: line.arguments[1],
nicks: this.conn.queue['names'][line.arguments[1]],
server: serverName
})
delete this.conn.queue['names'][line.arguments[1]]
}
if (Object.keys(this.conn.queue['names']).length === 0) {
delete this.conn.queue['names']
}
break
case 'PRIVMSG':
if (line.trailing.indexOf('\x01') === 0 && line.trailing.indexOf('\x01ACTION') !== 0) {
return this.ctcpManage(line)
}
if (line.user.nickname !== '') {
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'privmsg',
to: line.arguments[0],
user: line.user,
message: line.trailing,
server: serverName
})
} else {
this.conn.emit('pass_to_client', {
type: 'server_message',
messageType: 'privmsg',
message: line.trailing,
server: serverName,
from: realServerName
})
}
break
case 'NOTICE':
if (line.trailing.indexOf('\x01') === 0 && line.trailing.indexOf('\x01ACTION') !== 0) {
let composethis = line.trailing.replace(/\x01/g, '').trim().split(' ')
composethis[0] = composethis[0].toUpperCase()
let message = composethis.join(' ')
if (composethis[0] === 'PING') {
message = Math.floor(Date.now() / 1000) - composethis[1] + 's'
}
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'ctcp_response',
to: line.arguments[0],
user: line.user,
message: message,
server: serverName
})
return
}
if (line.user.nickname !== '') {
this.conn.emit('pass_to_client', {
type: 'message',
messageType: 'notice',
to: line.arguments[0],
user: line.user,
message: line.trailing,
server: serverName
})
} else {
this.conn.emit('pass_to_client', {
type: 'server_message',
messageType: 'notice',
message: line.trailing,
server: serverName,
from: realServerName
})
}
break
case 'NICK':
if (line.user.nickname === this.conn.config.nickname) {
this.conn.config.nickname = line.arguments[0]
}
this.conn.emit('pass_to_client', {
type: 'nick_change', nick: line.user.nickname, newNick: line.arguments[0], server: serverName
})
break
case 'KICK':
this.conn.emit('pass_to_client', {
type: 'event_kick_channel', user: line.user, channel: line.arguments[0], reason: line.trailing, kickee: line.arguments[1], server: serverName
})
break
case 'TOPIC':
this.conn.emit('pass_to_client', {
type: 'channel_topic', channel: line.arguments[0], set_by: line.user.nickname, topic: line.trailing, server: serverName
})
break
case '332':
this.conn.emit('pass_to_client', {
type: 'channel_topic', channel: line.arguments[1], topic: line.trailing, server: serverName
})
break
case '333':
this.conn.emit('pass_to_client', {
type: 'channel_topic', channel: line.arguments[1], set_by: line.arguments[2], time: line.arguments[3], server: serverName
})
break
case '375':
case '372':
case '376':
this.conn.emit('pass_to_client', {
type: 'server_message', messageType: 'motd', message: line.trailing, server: serverName, from: realServerName
})
break
case '006':
case '007':
case '251':
case '255':
case '270':
case '290':
case '292':
case '323':
case '351':
case '381':
this.conn.emit('pass_to_client', {
type: 'server_message', messageType: 'regular', message: line.trailing, server: serverName, from: realServerName
})
break
case '252':
case '254':
case '396':
case '042':
this.conn.emit('pass_to_client', {
type: 'server_message', messageType: 'regular', message: line.arguments[1] + ' ' + line.trailing, server: serverName, from: realServerName
})
break
case '501':
case '401':
case '402':
case '421':
case '482':
case '331':
case '432':
this.conn.emit('pass_to_client', {
type: 'message',
to: null,
message: line.arguments[1] + ': ' + line.trailing,
server: serverName,
user: {
nickname: realServerName
},
messageType: 'error'
})
break
case 'MODE':
let isChannelMode = false
let method = '+'
if (line.arguments[0].indexOf('#') !== -1) {
isChannelMode = true
}
let modes = line.arguments[1]
if (!modes && line.trailing !== '') {
modes = line.trailing
}
let sender = line.user.nickname
if (sender === '') {
sender = line.user.hostname
}
method = modes.substring(0, 1)
modes = modes.substring(1).split('')
let pass = []
if (isChannelMode) {
for (let i in modes) {
let mode = modes[i]
if (this.conn.data.supportedModes[mode]) {
this.conn.emit('pass_to_client', {
type: 'mode_' + (method === '+' ? 'add' : 'del'),
target: line.arguments[0],
mode: mode,
modeTarget: line.arguments[2 + parseInt(i)],
server: serverName,
user: {
nickname: sender
}
})
} else {
pass.push(mode)
}
}
} else {
pass = modes
}
if (pass.length > 0) {
this.conn.emit('pass_to_client', {
type: 'mode',
target: line.arguments[0],
message: method + pass.join(''),
server: serverName,
user: {
nickname: sender
}
})
}
break
case '433':
let newNick = this.conn.config.nickname + '_'
this.conn.write('NICK ' + newNick)
this.conn.config.nickname = newNick
break
case '311':
// start whois queue
list = {
nickname: line.arguments[1],
hostmask: '%s!%s@%s'.format(line.arguments[1], line.arguments[2], line.arguments[3]),
realname: line.trailing || ''
}
this.whoisManage(line.arguments[1], list)
break
case '319':
// whois: channels
list = {
channels: line.trailing.split(' ')
}
this.whoisManage(line.arguments[1], list)
break
case '378':
list = {
connectingFrom: line.trailing
}
this.whoisManage(line.arguments[1], list)
break
case '379':
list = {
usingModes: line.trailing
}
this.whoisManage(line.arguments[1], list)
break
case '312':
list = {
server: line.arguments[2],
server_name: line.trailing || ''
}
this.whoisManage(line.arguments[1], list)
break
case '313':
list = {
title: line.trailing
}
this.whoisManage(line.arguments[1], list)
break
case '330':
list = {
loggedIn: line.trailing + ' ' + line.arguments[2]
}
this.whoisManage(line.arguments[1], list)
break
case '335':
list = {
bot: true
}
this.whoisManage(line.arguments[1], list)
break
case '307':
list = {
registered: line.trailing
}
this.whoisManage(line.arguments[1], list)
break
case '671':
list = {
secure: true
}
this.whoisManage(line.arguments[1], list)
break
case '317':
list = {
signonTime: line.arguments[3],
idleSeconds: line.arguments[2]
}
this.whoisManage(line.arguments[1], list)
break
case '318':
if (!this.conn.queue.whois || !this.conn.queue.whois[line.arguments[1]]) return
this.conn.emit('pass_to_client', {
type: 'whoisResponse',
whois: this.conn.queue.whois[line.arguments[1]],
server: serverName,
from: realServerName
})
delete this.conn.queue.whois[line.arguments[1]]
break
case '321':
this.conn.emit('pass_to_client', {
type: 'listedchan',
channel: 'Channel',
users: 'Users',
topic: 'Topic',
server: serverName,
from: realServerName
})
break
case '322':
this.conn.emit('pass_to_client', {
type: 'listedchan',
channel: line.arguments[1],
users: line.arguments[2],
topic: line.trailing,
server: serverName,
from: realServerName
})
break
case 'CAP':
// might come in the future, who knows
this.conn.write('CAP END')
break
}
}
}
class IRCConnection extends EventEmitter {
constructor (providedInfo, globalConfig, extras) {
super()
this.globalConfig = globalConfig
this.extras = extras || { authenticationSteps: [], ctcps: {} }
this.config = {
nickname: 'teemant',
username: globalConfig.username,
realname: globalConfig.realname,
server: 'localhost',
port: 6667,
autojoin: [],
secure: globalConfig.secure_by_default,
password: '',
address: providedInfo.server,
rejectUnauthorized: globalConfig.rejectUnauthorizedCertificates
}
for (let a in providedInfo) {
this.config[a] = providedInfo[a]
}
this.socket = null
this.connected = false
this.authenticated = false
this.handler = new IRCConnectionHandler(this)
this.data = {
serverSupports: {},
network: this.config.server,
actualServer: this.config.server,
maxChannelLength: 64,
supportedModes: {}
}
this.authorizationError = ''
this.queue = {}
}
connect () {
this.socket = (this.config.secure ? tls : net).connect({
port: this.config.port,
host: this.config.server,
rejectUnauthorized: this.config.rejectUnauthorized
}, () => {
this.connected = true
this.authenticate()
})
this.socket.setEncoding(this.globalConfig.encoding)
this.socket.setTimeout(this.globalConfig.timeout)
this.socket.on('error', (data) => {
this.emit('connerror', { type: 'sock_error', message: 'A socket error occured.', raw: data })
})
this.socket.on('lookup', (err, address, family, host) => {
if (err) {
this.emit('connerror', { type: 'resolve_error', message: 'Failed to resolve host.' })
} else {
this.emit('lookup', { address: address, family: address, host: host })
this.config.address = address
}
})
let buffer = ''
this.socket.on('data', (chunk) => {
buffer += chunk
let data = buffer.split('\r\n')
buffer = data.pop()
data.forEach((line) => {
if (line.indexOf('PING') === 0) {
this.socket.write('PONG %s\r\n', line.substring(4))
return
}
this.emit('raw', line)
let parsed = parse(line)
this.emit('line', parsed)
this.handler.handleServerLine(parsed)
})
})
this.socket.on('close', (data) => {
if (!this.queue['close']) {
this.emit('closed', { type: 'sock_closed', raw: data, message: 'Connection closed.' })
}
this.connected = false
this.authenticated = false
})
}
authenticate () {
if (this.config.password) {
this.socket.write('PASS %s\r\n', this.config.password)
}
if (this.extras.authenticationSteps) {
for (let i in this.extras.authenticationSteps) {
let step = this.extras.authenticationSteps[i]
step.authenticate(this)
}
}
this.socket.write('USER %s 8 * :%s\r\n', this.config.username, this.config.realname)
this.socket.write('NICK %s\r\n', this.config.nickname)
}
disconnect (message) {
if (!this.connected) {
this.emit('connerror', { type: 'sock_closed', message: 'Connection already closed.' })
return
}
this.queue['close'] = true
this.socket.write('QUIT :%s\r\n', (message != null ? message : this.globalConfig.default_quit_msg))
}
write () {
let message = util.format.apply(null, arguments)
if (!this.connected) {
return this.emit('connerror', { type: 'sock_closed', message: 'Connection is closed.' })
}
this.socket.write(message + '\r\n')
}
}
module.exports = IRCConnection

62
server/irc/parser.js Normal file
View File

@ -0,0 +1,62 @@
// :nickname!username@hostname command arg ume nts :trailing
// or
// :hostname command arg ume nts :trailing
function parseERROR (line) {
let final = {
user: { nickname: '', username: '', hostname: '' },
command: 'ERROR',
message: '',
raw: line.join(' ')
}
let pass1 = line.slice(1).join(' ')
if (pass1.indexOf(':') === 0) {
pass1 = pass1.substring(1)
}
final.message = pass1
return final
}
module.exports = function (rawline) {
let final = {
user: {
nickname: '',
username: '',
hostname: ''
},
command: '',
arguments: [],
trailing: '',
raw: rawline
}
let pass1 = (rawline.indexOf(':') === 0 ? rawline.substring(1).split(' ') : rawline.split(' '))
if (pass1[0] === 'ERROR') return parseERROR(pass1)
if (pass1[0].indexOf('!') !== -1) {
let nickuser = pass1[0].split('!')
final.user.nickname = nickuser[0]
let userhost = nickuser[1].split('@')
final.user.username = userhost[0]
final.user.hostname = userhost[1]
} else {
final.user.hostname = pass1[0]
}
final.command = pass1[1]
let pass2 = pass1.slice(2).join(' ')
if (pass2.indexOf(':') !== -1) {
final.arguments = pass2.substring(0, pass2.indexOf(' :')).split(' ')
final.trailing = pass2.substring(pass2.indexOf(':') + 1)
} else {
final.arguments = pass2.split(' ')
}
return final
}

View File

@ -1,50 +1,52 @@
const util = require('util');
const config = require(__dirname+'/config');
import util from 'util'
import config from './config'
module.exports.log = function() {
console.log.apply(null, arguments);
};
module.exports.log = function () {
console.log.apply(null, arguments)
}
module.exports.debugLog = function() {
if(!config.server.debug) return;
module.exports.debugLog = function () {
if (!config.server.debug) return
console.log.apply(null, arguments);
};
console.log.apply(null, arguments)
}
module.exports.errorLog = function(errObj, specify) {
if(specify)
console.error(specify);
module.exports.errorLog = function (errObj, specify) {
if (specify) console.error(specify)
console.error(errObj)
console.error(errObj);
if (errObj.stack) {
console.error(errObj.stack)
}
}
if(errObj.stack)
console.error(errObj.stack);
};
module.exports.printRuntimeStats = function (runtimeStats, connections) {
if (!config.server.printStats) return
module.exports.printRuntimeStats = function(runtime_stats, connections) {
if(!config.server.printStats) return;
let date = new Date()
let users = 0
let servers = 0
let serversPerUser = 0
let date = new Date();
let users = 0;
let servers = 0;
let serversPerUser = 0;
for(let uid in connections) {
let ca = connections[uid];
users += 1;
for(let snam in ca) {
if(!snam) continue;
if(snam == 'host') continue;
servers += 1;
for (let uid in connections) {
let ca = connections[uid]
users += 1
for (let snam in ca) {
if (!snam) continue
if (snam === 'host') continue
servers += 1
}
}
if(users != 0) // Don't divide by zero lmao
serversPerUser = servers/users;
if (users !== 0) { // Don't divide by zero lmao
serversPerUser = servers / users
}
console.log(date+(': Currently connected users: {0}. ' +
'IRC server connections: {1}. ' +
'Average servers per user: {2}. ' +
'Total connections made: {3}. ' +
'Uptime: {4}s;').format(users, servers, serversPerUser, runtime_stats.connectionsMade, process.uptime()));
};
console.log(date,
util.format('- Currently connected users: %d. ' +
'IRC server connections: %d. ' +
'Average servers per user: %f. ' +
'Total connections made: %d. ' +
'Uptime: %d s;', users, servers, serversPerUser, runtimeStats.connectionsMade, process.uptime())
)
}

View File

@ -1,7 +0,0 @@
let connector = require(__dirname+'/irc.js');
let parser = require(__dirname+'/parser.js');
module.exports = {
IRCConnection: connector,
Parser: parser
};

View File

@ -1,549 +0,0 @@
const EventEmitter = require('events').EventEmitter;
const net = require('net');
const tls = require('tls');
const parse = require(__dirname+'/parser');
class IRCConnectionHandler {
constructor(connection) {
this.conn = connection;
}
handleUserLine(data) {
switch(data.command) {
case 'topic':
this.conn.write(('{0} {1}'+(data.message != '' ? ' :'+data.message : '')).format(data.command.toUpperCase(), data.arguments[0]));
break;
case 'kick':
this.conn.write('{0} {1} :{2}'.format(data.command.toUpperCase(), data.arguments.join(' '), data.message));
break;
case 'part':
this.conn.write('{0} {1} :{2}'.format(data.command.toUpperCase(), data.arguments[0], data.message));
break;
case 'nick':
case 'whois':
case 'who':
case 'names':
case 'join':
this.conn.write('{0} {1}'.format(data.command.toUpperCase(), data.arguments[0]));
break;
case 'quit':
this.conn.write('{0} :{1}'.format(data.command.toUpperCase(), (data.message == '' ?
this.conn.globalConfig.default_quit_msg : data.message)));
break;
case 'privmsg':
this.conn.write('PRIVMSG {0} :{1}'.format(data.arguments[0], data.message));
this.conn.emit('pass_to_client', {type: 'message', messageType: 'privmsg', to: data.arguments[0],
user: {nickname: this.conn.config.nickname}, message: data.message, server: data.server});
break;
case 'notice':
this.conn.write('NOTICE {0} :{1}'.format(data.arguments[0], data.message));
this.conn.emit('pass_to_client', {type: 'message', messageType: 'notice', to: data.arguments[0],
user: {nickname: this.conn.config.nickname}, message: data.message, server: data.server});
break;
case 'list':
this.conn.write(data.command.toUpperCase());
break;
case 'ctcp':
let ctcpmsg = '';
if(data.arguments[1].toLowerCase() == 'ping')
ctcpmsg = 'PING '+Math.floor(Date.now()/1000);
else
ctcpmsg = data.message;
this.conn.write('PRIVMSG {0} :\x01{1}\x01'.format(data.arguments[0], ctcpmsg));
this.conn.emit('pass_to_client', {type: 'message', messageType: 'ctcp_request', to: this.conn.config.nickname,
user: {nickname: data.arguments[0]}, message: ctcpmsg, server: data.server});
break;
default:
this.conn.write(data.command.toUpperCase()+' '+data.message);
}
if(data.targetType == 'channel' || data.targetType == 'message') {
this.conn.write('PRIVMSG {0} :{1}'.format(data.target, data.message));
this.conn.emit('pass_to_client', {type: 'message', messageType: 'privmsg', to: data.target,
user: {nickname: this.conn.config.nickname}, message: data.message, server: data.server});
}
}
whoisManage(whom, list) {
if(!this.conn.queue.whois)
this.conn.queue.whois = {};
if(!this.conn.queue.whois[whom])
this.conn.queue.whois[whom] = list;
else
for(let a in list)
this.conn.queue.whois[whom][a] = list[a];
}
ctcpManage(data) {
let line = data.trailing.replace(/\x01/g, '').trim().split(' ');
if(!line[0]) return;
line[0] = line[0].toUpperCase();
let resp = '\x01'+line[0]+' {0}\x01';
if(line[0] == 'PING' && line[1] != null && line[1] != '') {
resp = resp.format(line.slice(1).join(' '));
} else if(line[0] == 'CLIENTINFO') {
resp = resp.format('CLIENTINFO PING '+Object.keys(this.conn.extras.ctcps).join(' '));
} else if(this.conn.extras.ctcps && this.conn.extras.ctcps[line[0]] != null) {
resp = resp.format(this.conn.extras.ctcps[line[0]](data, this.conn));
} else {
resp = null;
}
if (resp != null)
this.conn.write('NOTICE {0} :{1}'.format(data.user.nickname, resp));
return resp != null;
}
handleServerLine(line) {
if(this.conn.queue['supportsmsg'] && line.command != '005') {
delete this.conn.queue['supportsmsg'];
if(this.conn.config.autojoin.length > 0)
for(let t in this.conn.config.autojoin)
this.conn.write('JOIN '+this.conn.config.autojoin[t]);
this.conn.emit('authenticated', {});
}
let serverName = this.conn.config.server;
let realServerName = this.conn.data.actualServer;
if(line.user.nickname == '')
realServerName = line.user.hostname;
let list = null;
switch(line.command) {
case 'error':
this.conn.emit('connerror', {type: 'irc_error', raw: line.raw});
break;
case '001':
this.conn.data.actualServer = line.user.hostname;
break;
case '005':
if(!this.conn.queue['supportsmsg'])
this.conn.queue['supportsmsg'] = true;
this.conn.authenticated = true;
let argv = line.arguments.slice(1);
for(let a in argv) {
let t = argv[a];
if(t.indexOf('=') != -1) {
t = t.split('=');
if(t[0] === 'PREFIX') {
let d = t[1].match(/\((\w+)\)(.*)/);
let r = d[1].split('');
let aa = d[2].split('');
for(let b in r)
this.conn.data.supportedModes[r[b]] = aa[b];
} else if(t[0] === 'NETWORK') {
this.conn.data.network = t[1];
} else if(t[0] === 'CHANNELLEN') {
this.conn.data.max_channel_length = parseInt(t[1]);
}
this.conn.data.serverSupports[t[0]] = t[1];
} else {
this.conn.data.serverSupports[t] = true;
}
}
break;
case 'JOIN':
if(line.trailing) {
this.conn.emit('pass_to_client', {type: 'event_join_channel', user: line.user, channel: line.trailing, server: serverName });
} else {
for(let i in line.arguments) {
this.conn.emit('pass_to_client', {type: 'event_join_channel', user: line.user, channel: line.arguments[i], server: serverName });
}
}
break;
case 'PART':
this.conn.emit('pass_to_client', {type: 'event_part_channel', user: line.user, channel: line.arguments[0], reason: line.trailing, server: serverName });
break;
case 'QUIT':
this.conn.emit('pass_to_client', {type: 'event_quit', user: line.user, reason: line.trailing, server: serverName });
break;
case '353':
if(!this.conn.queue['names'])
this.conn.queue['names'] = {};
let splittrail = line.trailing.split(' ');
for(let a in splittrail) {
let nick = splittrail[a];
if(nick.trim() == '') continue;
if(this.conn.queue['names'][line.arguments[2]])
this.conn.queue['names'][line.arguments[2]].push(nick);
else
this.conn.queue['names'][line.arguments[2]] = [nick];
}
break;
case '366':
if(!this.conn.queue['names']) break;
if(this.conn.queue['names'][line.arguments[1]]) {
this.conn.emit('pass_to_client', {type: 'channel_nicks', channel: line.arguments[1], nicks: this.conn.queue['names'][line.arguments[1]], server: serverName});
delete this.conn.queue['names'][line.arguments[1]];
}
if(Object.keys(this.conn.queue['names']).length == 0)
delete this.conn.queue['names'];
break;
case 'PRIVMSG':
if(line.trailing.indexOf('\x01') == 0 && line.trailing.indexOf('\x01ACTION') != 0)
return this.ctcpManage(line);
if(line.user.nickname != '')
this.conn.emit('pass_to_client', {type: 'message', messageType: 'privmsg', to: line.arguments[0],
user: line.user, message: line.trailing, server: serverName});
else
this.conn.emit('pass_to_client', {type: 'server_message', messageType: 'privmsg', message: line.trailing, server: serverName, from: realServerName});
break;
case 'NOTICE':
if(line.trailing.indexOf('\x01') == 0 && line.trailing.indexOf('\x01ACTION') != 0) {
let composethis = line.trailing.replace(/\x01/g,'').trim().split(' ');
composethis[0] = composethis[0].toUpperCase();
let message = composethis.join(' ');
if(composethis[0] == 'PING')
message = Math.floor(Date.now()/1000) - composethis[1]+'s';
this.conn.emit('pass_to_client', {type: 'message', messageType: 'ctcp_response', to: line.arguments[0],
user: line.user, message: message, server: serverName});
return;
}
if(line.user.nickname != '')
this.conn.emit('pass_to_client', {type: 'message', messageType: 'notice', to: line.arguments[0],
user: line.user, message: line.trailing, server: serverName});
else
this.conn.emit('pass_to_client', {type: 'server_message', messageType: 'notice', message: line.trailing, server: serverName, from: realServerName});
break;
case 'NICK':
if(line.user.nickname == this.conn.config.nickname)
this.conn.config.nickname = line.arguments[0];
this.conn.emit('pass_to_client', {type: 'nick_change', nick: line.user.nickname, newNick: line.arguments[0], server: serverName});
break;
case 'KICK':
this.conn.emit('pass_to_client', {type: 'event_kick_channel', user: line.user, channel: line.arguments[0], reason: line.trailing, kickee: line.arguments[1], server: serverName});
break;
case 'TOPIC':
this.conn.emit('pass_to_client', {type: 'channel_topic', channel: line.arguments[0], set_by: line.user.nickname, topic: line.trailing, server: serverName});
break;
case '332':
this.conn.emit('pass_to_client', {type: 'channel_topic', channel: line.arguments[1], topic: line.trailing, server: serverName});
break;
case '333':
this.conn.emit('pass_to_client', {type: 'channel_topic', channel: line.arguments[1], set_by: line.arguments[2], time: line.arguments[3], server: serverName});
break;
case '375':
case '372':
case '376':
this.conn.emit('pass_to_client', {type: 'server_message', messageType: 'motd', message: line.trailing, server: serverName, from: realServerName});
break;
case '006':
case '007':
case '251':
case '255':
case '270':
case '290':
case '292':
case '323':
case '351':
case '381':
this.conn.emit('pass_to_client', {type: 'server_message', messageType: 'regular', message: line.trailing, server: serverName, from: realServerName});
break;
case '252':
case '254':
case '396':
case '042':
this.conn.emit('pass_to_client', {type: 'server_message', messageType: 'regular', message: line.arguments[1] +' '+ line.trailing, server: serverName, from: realServerName});
break;
case '501':
case '401':
case '402':
case '421':
case '482':
case '331':
case '432':
this.conn.emit('pass_to_client', {type: 'message', to: null, message: line.arguments[1]+': '+line.trailing,
server: serverName, user: {nickname: realServerName}, messageType: 'error'});
break;
case 'MODE':
let isChannelMode = false;
let method = '+';
if(line.arguments[0].indexOf('#') != -1)
isChannelMode = true;
let modes = line.arguments[1];
if(!modes && line.trailing != '')
modes = line.trailing;
let sender = line.user.nickname;
if(sender == '')
sender = line.user.hostname;
method = modes.substring(0, 1);
modes = modes.substring(1).split('');
let pass = [];
if(isChannelMode) {
for(let i in modes) {
let mode = modes[i];
if(this.conn.data.supportedModes[mode])
this.conn.emit('pass_to_client', {type: 'mode_'+(method=='+'?'add':'del'), target: line.arguments[0], mode: mode,
modeTarget: line.arguments[2+parseInt(i)], server: serverName, user: {nickname: sender}});
else
pass.push(mode);
}
} else {
pass = modes;
}
if(pass.length > 0)
this.conn.emit('pass_to_client', {type: 'mode', target: line.arguments[0], message: method+pass.join(''),
server: serverName, user: {nickname: sender}});
break;
case '433':
let newNick = this.conn.config.nickname + '_';
this.conn.write('NICK '+newNick);
this.conn.config.nickname = newNick;
break;
case '311':
// start whois queue
list = {
nickname: line.arguments[1],
hostmask: '{0}!{1}@{2}'.format(line.arguments[1], line.arguments[2], line.arguments[3]),
realname: line.trailing || ''
};
this.whoisManage(line.arguments[1], list);
break;
case '319':
// whois: channels
list = {
channels: line.trailing.split(' '),
};
this.whoisManage(line.arguments[1], list);
break;
case '378':
list = {
connectingFrom: line.trailing,
};
this.whoisManage(line.arguments[1], list);
break;
case '379':
list = {
usingModes: line.trailing,
};
this.whoisManage(line.arguments[1], list);
break;
case '312':
list = {
server: line.arguments[2],
server_name: line.trailing || ''
};
this.whoisManage(line.arguments[1], list);
break;
case '313':
list = {
title: line.trailing
};
this.whoisManage(line.arguments[1], list);
break;
case '330':
list = {
loggedIn: line.trailing+' '+line.arguments[2]
};
this.whoisManage(line.arguments[1], list);
break;
case '335':
list = {
bot: true
};
this.whoisManage(line.arguments[1], list);
break;
case '307':
list = {
registered: line.trailing
};
this.whoisManage(line.arguments[1], list);
break;
case '671':
list = {
secure: true
};
this.whoisManage(line.arguments[1], list);
break;
case '317':
list = {
signonTime: line.arguments[3],
idleSeconds: line.arguments[2]
};
this.whoisManage(line.arguments[1], list);
break;
case '318':
if(!this.conn.queue.whois || !this.conn.queue.whois[line.arguments[1]])
return;
this.conn.emit('pass_to_client', {type: 'whoisResponse', whois: this.conn.queue.whois[line.arguments[1]],
server: serverName, from: realServerName});
delete this.conn.queue.whois[line.arguments[1]];
break;
case '321':
this.conn.emit('pass_to_client', {type: 'listedchan', channel: 'Channel', users: 'Users', topic: 'Topic',
server: serverName, from: realServerName});
break;
case '322':
this.conn.emit('pass_to_client', {type: 'listedchan', channel: line.arguments[1], users: line.arguments[2], topic: line.trailing,
server: serverName, from: realServerName});
break;
case 'CAP':
// might come in the future, who knows
this.conn.write('CAP END');
break;
}
}
}
class IRCConnection extends EventEmitter {
constructor(providedInfo, globalConfig, extras) {
super();
this.globalConfig = globalConfig;
this.extras = extras || { authenticationSteps: [], ctcps: {} };
this.config = {
nickname: 'teemant',
username: globalConfig.username,
realname: globalConfig.realname,
server: 'localhost',
port: 6667,
autojoin: [],
secure: globalConfig.secure_by_default,
password: '',
address: providedInfo.server,
rejectUnauthorized: globalConfig.rejectUnauthorizedCertificates
};
for(let a in providedInfo) {
this.config[a] = providedInfo[a];
}
this.socket = null;
this.connected = false;
this.authenticated = false;
this.handler = new IRCConnectionHandler(this);
this.data = {
serverSupports: {},
network: this.config.server,
actualServer: this.config.server,
max_channel_length: 64,
supportedModes: {}
};
this.authorizationError = '';
this.queue = {};
}
on(...args) {
return super.on(...args);
}
emit(...args) {
return super.emit(...args);
}
connect() {
this.socket = (this.config.secure ? tls : net).connect({port: this.config.port, host: this.config.server,
rejectUnauthorized: this.config.rejectUnauthorized}, () => {
this.connected = true;
this.authenticate();
});
this.socket.setEncoding(this.globalConfig.encoding);
this.socket.setTimeout(this.globalConfig.timeout);
this.socket.on('error', (data) => {
this.emit('connerror', {type: 'sock_error', message: 'A socket error occured.', raw: data});
});
this.socket.on('lookup', (err, address, family, host) => {
if(err) {
this.emit('connerror', {type: 'resolve_error', message: 'Failed to resolve host.'});
} else {
this.emit('lookup', {address: address, family: address, host: host});
this.config.address = address;
}
});
let buffer = '';
this.socket.on('data', (chunk) => {
buffer += chunk;
let data = buffer.split('\r\n');
buffer = data.pop();
data.forEach((line) => {
if(line.indexOf('PING') === 0) {
this.socket.write('PONG'+line.substring(4)+'\r\n');
return;
}
this.emit('raw', line);
let parsed = parse(line);
this.emit('line', parsed);
this.handler.handleServerLine(parsed);
});
});
this.socket.on('close', (data) => {
if(!this.queue['close'])
this.emit('closed', {type: 'sock_closed', raw: data, message: 'Connection closed.'});
this.connected = false;
this.authenticated = false;
});
}
authenticate() {
if (this.config.password)
this.socket.write('PASS {0}\r\n'.format(this.config.password));
if(this.extras.authenticationSteps) {
for(let i in this.extras.authenticationSteps) {
let step = this.extras.authenticationSteps[i];
step.authenticate(this);
}
}
this.socket.write('USER {0} 8 * :{1}\r\n'.format(this.config.username, this.config.realname));
this.socket.write('NICK {0}\r\n'.format(this.config.nickname));
}
disconnect(message) {
if(!this.connected) {
this.emit('connerror', {type: 'sock_closed', message: 'Connection already closed.'});
return;
}
this.queue['close'] = true;
this.socket.write('QUIT :{0}\r\n'.format(message != null ? message : this.globalConfig.default_quit_msg));
}
write(message) {
if(!this.connected) {
this.emit('connerror', {type: 'sock_closed', message: 'Connection is closed.'});
return;
}
this.socket.write(message+'\r\n');
}
}
module.exports = IRCConnection;

View File

@ -1,61 +0,0 @@
// :nickname!username@hostname command arg ume nts :trailing
// or
// :hostname command arg ume nts :trailing
function parseERROR(line) {
let final = {
user: { nickname: '', username: '', hostname: '' },
command: 'ERROR',
message: '',
raw: line.join(' ')
};
let pass1 = line.slice(1).join(' ');
if(pass1.indexOf(':') == 0)
pass1 = pass1.substring(1);
final.message = pass1;
return final;
}
module.exports = function(rawline) {
let final = {
user: {
nickname: '',
username: '',
hostname: ''
},
command: '',
arguments: [],
trailing: '',
raw: rawline
};
let pass1 = (rawline.indexOf(':') == 0 ? rawline.substring(1).split(' ') : rawline.split(' '));
if (pass1[0] === 'ERROR')
return parseERROR(pass1);
if(pass1[0].indexOf('!') != -1) {
let nickuser = pass1[0].split('!');
final.user.nickname = nickuser[0];
let userhost = nickuser[1].split('@');
final.user.username = userhost[0];
final.user.hostname = userhost[1];
} else {
final.user.hostname = pass1[0];
}
final.command = pass1[1];
let pass2 = pass1.slice(2).join(' ');
if(pass2.indexOf(':') != -1) {
final.arguments = pass2.substring(0, pass2.indexOf(' :')).split(' ');
final.trailing = pass2.substring(pass2.indexOf(':')+1);
} else {
final.arguments = pass2.split(' ');
}
return final;
};

View File

@ -1,122 +1,120 @@
const dns = require('dns');
const fs = require('fs');
const path = require('path');
const config = require(__dirname+'/config');
const logger = require(__dirname+'/logger');
const webirc_data_path = path.resolve(__dirname+'/../webirc.data.json');
import dns from 'dns'
import fs from 'fs'
import path from 'path'
let webirc_data = {};
let timeouts = {};
import config from './server/config'
import logger from './server/logger'
function writeToFile() {
fs.writeFile(webirc_data_path, JSON.stringify(webirc_data, null, '\t'), function (err) {if (err) throw err;});
const webircDataPath = path.join(__dirname, '..', 'webirc.data.json')
let webircData = {}
let timeouts = {}
function writeToFile () {
fs.writeFile(webircDataPath, JSON.stringify(webircData, null, '\t'), (err) => { if (err) throw err })
}
function timeoutRefresh(address, seconds) {
if(timeouts[address])
clearTimeout(timeouts[address]);
function timeoutRefresh (address, seconds) {
if (timeouts[address]) clearTimeout(timeouts[address])
timeouts[address] = setTimeout(()=>{resolveAddress(address);}, seconds*1000);
timeouts[address] = setTimeout(() => resolveAddress(address), seconds * 1000)
}
function resolveAddress(address, force) {
logger.debugLog('** WEBIRC ** Attempting to update IP for '+address);
let obj = webirc_data[address];
function resolveAddress (address, force) {
logger.debugLog('** WEBIRC ** Attempting to update IP for', address)
let obj = webircData[address]
if(!obj) return;
if (!obj) return
if((Date.now() - obj.last_update)/1000 < config.webirc.resolveInterval && !force) {
let nextTime = (config.webirc.resolveInterval - (Date.now() - obj.last_update)/1000);
if ((Date.now() - obj.last_update) / 1000 < config.webirc.resolveInterval && !force) {
let nextTime = (config.webirc.resolveInterval - (Date.now() - obj.last_update) / 1000)
logger.debugLog('** WEBIRC ** {0} IP is {1}, refresh in {2} seconds'.format(address, obj.cached_ip, Math.floor(nextTime)));
logger.debugLog(`** WEBIRC ** ${address} IP is ${obj.cachedIP}, refresh in ${Math.floor(nextTime)} seconds`)
return timeoutRefresh(address, nextTime);
return timeoutRefresh(address, nextTime)
}
new Promise((resolve, reject) => {
if(address === '127.0.0.1' || address === '0.0.0.0' || address === 'localhost') {
logger.debugLog('** WEBIRC ** Ignoring localhost entry..');
return resolve('127.0.0.1');
if (address === '127.0.0.1' || address === '0.0.0.0' || address === 'localhost') {
logger.debugLog('** WEBIRC ** Ignoring localhost entry..')
return resolve('127.0.0.1')
}
dns.resolve(address, (err, data) => {
if(err!=null) return reject(err);
let ip = data.length > 0 ? data[0] : null;
if (err != null) return reject(err)
let ip = data.length > 0 ? data[0] : null
if(ip) {
resolve(ip);
if (ip) {
resolve(ip)
} else {
reject(new Error('no ips'));
reject(new Error('no ips'))
}
});
})
}).then((data) => {
logger.debugLog('** WEBIRC ** Updated DNS for {0}; IP is now {1}'.format(address, data));
logger.debugLog('** WEBIRC ** Updated DNS for {0}; IP is now {1}'.format(address, data))
webirc_data[address].last_update = Date.now();
webirc_data[address].cached_ip = data;
webircData[address].last_update = Date.now()
webircData[address].cachedIP = data
writeToFile();
timeoutRefresh(address, config.webirc.resolveInterval);
writeToFile()
timeoutRefresh(address, config.webirc.resolveInterval)
}, (err) => {
logger.debugLog('** WEBIRC ** Failed to updated DNS for {0}; IP is still {1}'.format(address, webirc_data[address].cached_ip));
logger.debugLog(`** WEBIRC ** Failed to updated DNS for ${address}; IP is still ${webircData[address].cachedIP}:`, err.message)
timeoutRefresh(address, (config.webirc.resolveInterval+60));
});
timeoutRefresh(address, config.webirc.resolveInterval + 60)
})
}
function reload(force) {
if(!config.webirc.enabled) return;
function reload (force) {
if (!config.webirc.enabled) return
try {
fs.accessSync(webirc_data_path, fs.F_OK);
fs.accessSync(webircDataPath, fs.F_OK)
webirc_data = require(webirc_data_path);
webircData = require(webircDataPath)
if (require.cache && require.cache[webirc_data_path]) {
delete require.cache[webirc_data_path];
if (require.cache && require.cache[webircDataPath]) {
delete require.cache[webircDataPath]
}
} catch(e) {
writeToFile();
} catch (e) {
writeToFile()
}
for(let adr in webirc_data) {
resolveAddress(adr, force);
for (let adr in webircData) {
resolveAddress(adr, force)
}
}
function get_password(server_ip) {
let ip = null;
for(let a in webirc_data) {
if(webirc_data[a].cached_ip == server_ip)
ip = webirc_data[a];
function getPassword (serverIP) {
let ip = null
for (let a in webircData) {
if (webircData[a].cachedIP !== serverIP) continue
ip = webircData[a]
}
return ip;
return ip
}
class WebIRCAuthenticator {
constructor(userInfo) {
this.userInfo = userInfo;
constructor (userInfo) {
this.userInfo = userInfo
}
authenticate(connection) {
let serverpass = get_password(connection.config.address);
if(serverpass)
connection.socket.write('WEBIRC {0} {1} {2} {3}\r\n'.format(serverpass.password, connection.config.username,
this.userInfo.hostname, this.userInfo.ipaddr));
authenticate (connection) {
let serverpass = getPassword(connection.config.address)
if (serverpass) {
connection.socket.write(`WEBIRC ${serverpass.password} ${connection.config.username} ${this.userInfo.hostname} ${this.userInfo.ipaddr}\r\n`)
}
}
}
module.exports = {
reload: reload,
authenticator: WebIRCAuthenticator,
get_password: get_password,
writeToFile: writeToFile
};
Authenticator: WebIRCAuthenticator, reload, getPassword, writeToFile
}
process.on('SIGUSR1', () => {
logger.log('\n!!! Received SIGUSR1; Reloading webirc data.. !!!\n');
reload(true);
});
logger.log('\n!!! Received SIGUSR1; Reloading webirc data.. !!!\n')
reload(true)
})
reload(false);
reload(false)

View File

@ -1,99 +1,90 @@
#!/usr/bin/env node
'use strict';
const express = require('express');
const path = require('path');
const sockio = require('socket.io');
const dns = require('dns');
const app = express();
const router = express.Router();
'use strict'
import express from 'express'
import path from 'path'
import sockio from 'socket.io'
import dns from 'dns'
const pubdir = path.join(__dirname, 'build');
const pkg = require(__dirname+'/package.json');
import pkginfo from './package.json'
import irclib from './server/irc'
import webirc from './server/webirc'
const config = require(__dirname+'/server/config');
const logger = require(__dirname+'/server/logger');
import config from './server/config'
import logger from './server/logger'
const port = config.server.port || 8080;
const app = express()
const router = express.Router()
const pubdir = path.join(__dirname, 'build')
const port = config.server.port || 8080
const cacheAge = 365 * 24 * 60 * 60 * 1000
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != undefined ? args[number] : match;
});
};
let runtimeStats = {
connectionsMade: 0
}
let irclib = require(__dirname+'/server/teemant_irc');
let webirc = require(__dirname+'/server/webirc');
let runtime_stats = {
connectionsMade: 0
};
let connections = {};
let connections = {}
let customCTCPs = {
VERSION: function(data, connection) {
return 'TeemantIRC ver. {0} - {1} - https://teemant.icynet.ml/'.format(pkg.version, pkg.description);
VERSION: function (data, connection) {
return `TeemantIRC ver. ${pkginfo.version} - ${pkginfo.description} - https://teemant.icynet.eu/`
},
SOURCE: function(data, connection) {
return 'https://github.com/DiamondtechDev/TeemantIRC';
SOURCE: function (data, connection) {
return 'https://gitlab.icynet.eu/IcyNetwork/teemant'
}
};
}
process.stdin.resume();
process.stdin.resume()
router.get('/', function(req, res){
res.sendFile(pubdir+'/document/index.html');
});
router.get('/', function (req, res) {
res.sendFile(path.join(pubdir, '/document/index.html'))
})
router.get('/:server?*', function(req, res){
res.sendFile(pubdir+'/document/index.html');
});
router.get('/:server?*', function (req, res) {
res.sendFile(path.join(pubdir, '/document/index.html'))
})
app.use('/', express.static(pubdir, { maxAge: 365*24*60*60*1000 }));
app.use('/', express.static(pubdir+'/icons', { maxAge: 365*24*60*60*1000 }));
app.use('/', express.static(__dirname+'/static', { maxAge: 365*24*60*60*1000 }));
app.use('/', express.static(pubdir, { maxAge: cacheAge }))
app.use('/', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge }))
app.use('/', express.static(path.join(__dirname, 'static'), { maxAge: cacheAge }))
app.use('/:server', express.static(pubdir, { maxAge: 365*24*60*60*1000 }));
app.use('/:server', express.static(pubdir+'/icons', { maxAge: 365*24*60*60*1000 }));
app.use('/:server', express.static(pubdir+'/static', { maxAge: 365*24*60*60*1000 }));
app.use('/:server', express.static(pubdir, { maxAge: cacheAge }))
app.use('/:server', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge }))
app.use('/:server', express.static(path.join(pubdir, 'static'), { maxAge: cacheAge }))
app.use('/', router);
app.use('/', router)
const io = sockio.listen(app.listen(port, function() {
logger.log('*** Listening on http://localhost:{0}/'.format(port));
const io = sockio.listen(app.listen(port, function () {
logger.log(`*** Listening on http://localhost:${port}/`)
setInterval(() => {
logger.printRuntimeStats(runtime_stats, connections);
}, 3600000);
}));
logger.printRuntimeStats(runtimeStats, connections)
}, 3600000)
}))
function resolveHostname(ipaddr) {
let promise = new Promise(function(resolve, reject) {
dns.reverse(ipaddr, function(err, hostnames) {
if(err != null) return reject(err);
resolve(hostnames);
});
});
return promise;
function resolveHostname (ipaddr) {
return new Promise(function (resolve, reject) {
dns.reverse(ipaddr, function (err, hostnames) {
if (err != null) return reject(err)
resolve(hostnames)
})
})
}
io.sockets.on('connection', function (socket) {
let userip = socket.handshake.headers['x-real-ip'] || socket.handshake.headers['x-forwarded-for'] ||
socket.request.connection._peername.address || '127.0.0.1';
socket.request.connection._peername.address || '127.0.0.1'
if(userip.indexOf('::ffff:') == 0)
userip = userip.substring(7);
if (userip.indexOf('::ffff:') === 0) {
userip = userip.substring(7)
}
logger.debugLog('clientID: {0} from: {1}'.format(socket.id, userip));
logger.debugLog(`clientID: ${socket.id} from: ${userip}`)
socket.emit('defaults', {
server: config.client.default_server,
port: config.client.default_port,
ssl: config.client.secure_by_default
});
})
// New object for connections
connections[socket.id] = {
@ -101,117 +92,141 @@ io.sockets.on('connection', function (socket) {
ipaddr: userip,
hostname: userip
}
};
// Get the hostname of the connecting user
let hostQuery = resolveHostname(userip);
hostQuery.then((arr) => {
if(arr.length > 0)
connections[socket.id].host.hostname = arr[0];
logger.debugLog('Hostname of {0} was determined to be {1}'.format(socket.id, connections[socket.id].host.hostname));
}).catch((err) => {
logger.debugLog('Host resolve for {0} failed: {1}'.format(socket.id, err));
});
socket.on('disconnect', function() {
for (let d in connections[socket.id]) {
if(connections[socket.id][d].ipaddr) continue;
if(connections[socket.id][d].connected == true)
connections[socket.id][d].disconnect();
}
delete connections[socket.id];
// Get the hostname of the connecting user
let hostQuery = resolveHostname(userip)
hostQuery.then((arr) => {
if (arr.length > 0) {
connections[socket.id].host.hostname = arr[0]
}
logger.debugLog('clientID: {0} disconnect'.format(socket.id));
});
logger.debugLog(`Hostname of ${socket.id} was determined to be ${connections[socket.id].host.hostname}`)
}).catch((err) => {
logger.debugLog(`Host resolve for ${socket.id} failed:`, err.message)
})
socket.on('disconnect', function () {
for (let d in connections[socket.id]) {
if (connections[socket.id][d].ipaddr) continue
if (connections[socket.id][d].connected === true) {
connections[socket.id][d].disconnect()
}
}
delete connections[socket.id]
logger.debugLog('clientID: {0} disconnect'.format(socket.id))
})
socket.on('error', (e) => {
logger.errorLog(e, 'Socket error');
});
logger.errorLog(e, 'Socket error')
})
socket.on('userinput', (data) => {
let serv = connections[socket.id][data.server];
if(!serv) return;
if(serv.authenticated == false) return;
let serv = connections[socket.id][data.server]
if (!serv) return
if (serv.authenticated === false) return
logger.debugLog('['+socket.id+'] ->', data);
logger.debugLog(`[${socket.id}] ->`, data)
serv.handler.handleUserLine(data);
});
serv.handler.handleUserLine(data)
})
socket.on('irc_create', function(connectiondata) {
logger.debugLog(socket.id+' created irc connection: ', connectiondata);
socket.on('irc_create', function (connectiondata) {
logger.debugLog(`${socket.id} created irc connection: `, connectiondata)
socket.emit('act_client', {type: 'connect_message', message: 'Connecting to server..', error: false});
socket.emit('act_client', { type: 'connect_message', message: 'Connecting to server..', error: false })
if (connectiondata.port === null || connectiondata.server === null) {
return socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
message: 'Failed to connect to the server!', error: true});
return socket.emit('act_client', {
type: 'connect_message',
server: connectiondata.server,
message: 'Failed to connect to the server!',
error: true
})
}
let newConnection = new irclib.IRCConnection(connectiondata, config.client,
{
authenticationSteps: [
new webirc.authenticator(connections[socket.id].host)
new webirc.Authenticator(connections[socket.id].host)
],
ctcps: customCTCPs
});
})
newConnection.connect();
newConnection.connect()
connections[socket.id][connectiondata.server] = newConnection;
connections[socket.id][connectiondata.server] = newConnection
newConnection.on('authenticated', () => {
socket.emit('act_client', {type: 'event_connect', address: connectiondata.server, network: newConnection.data.network,
supportedModes: newConnection.data.supportedModes, nickname: newConnection.config.nickname,
max_channel_length: newConnection.data.max_channel_length});
socket.emit('act_client', {
type: 'event_connect',
address: connectiondata.server,
network: newConnection.data.network,
supportedModes: newConnection.data.supportedModes,
nickname: newConnection.config.nickname,
max_channel_length: newConnection.data.max_channel_length
})
runtime_stats.connectionsMade += 1;
});
runtimeStats.connectionsMade += 1
})
if(config.server.debug) {
newConnection.on('line', function(line) {
logger.debugLog('['+socket.id+'] <-', line);
});
if (config.server.debug) {
newConnection.on('line', function (line) {
logger.debugLog(`[${socket.id}] <-`, line)
})
newConnection.on('debug_log', function(data) {
logger.debugLog('['+socket.id+'] <-', data);
});
newConnection.on('debug_log', function (data) {
logger.debugLog(`[${socket.id}] <-`, data)
})
}
newConnection.on('connerror', (data) => {
logger.debugLog(data);
logger.debugLog(data)
if(newConnection.authenticated == false)
socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
message: 'Failed to connect to the server!', error: true});
});
if (newConnection.authenticated === false) {
socket.emit('act_client', {
type: 'connect_message',
server: connectiondata.server,
message: 'Failed to connect to the server!',
error: true
})
}
})
newConnection.on('pass_to_client', (data) => {
socket.emit('act_client', data);
});
socket.emit('act_client', data)
})
newConnection.on('closed', (data) => {
logger.debugLog(data);
logger.debugLog(data)
if(newConnection.authenticated == false)
socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
message: 'Failed to connect to the server!', error: true});
else
socket.emit('act_client', {type: 'event_server_quit', server: connectiondata.server});
});
});
});
if (newConnection.authenticated === false) {
socket.emit('act_client', {
type: 'connect_message',
server: connectiondata.server,
message: 'Failed to connect to the server!',
error: true
})
} else {
socket.emit('act_client', {
type: 'event_server_quit',
server: connectiondata.server
})
}
})
})
})
process.on('SIGINT', () => {
logger.log('!!! Received SIGINT; Terminating all IRC connections and exiting.. !!!');
logger.printRuntimeStats(runtime_stats, connections);
for(let c in connections) {
for(let ircconn in connections[c]) {
if(connections[c][ircconn].ipaddr) continue;
connections[c][ircconn].disconnect();
logger.log('!!! Received SIGINT; Terminating all IRC connections and exiting.. !!!')
logger.printRuntimeStats(runtimeStats, connections)
for (let c in connections) {
for (let ircconn in connections[c]) {
if (connections[c][ircconn].ipaddr) continue
connections[c][ircconn].disconnect()
}
}
process.exit();
});
process.exit(0)
})

View File

@ -1,49 +1,39 @@
'use strict';
'use strict'
const webpack = require('webpack');
const path = require('path');
let inProduction = process.env.NODE_ENV === 'production' || process.argv.indexOf('-p') !== -1;
const webpack = require('webpack')
const path = require('path')
module.exports = {
entry: {
main: ['./src/script/main']
main: './src/js/main'
},
output: {
path: __dirname,
filename: './build/script/[name].js',
chunkFilename: './build/script/[id].js'
path: path.join(__dirname, 'dist', 'js'),
filename: '[name].js'
},
module: {
preLoaders: [
// { test: /\.js$/, loader: 'eslint-loader', exclude: /node_modules/ },
// { test: /\.coffee$/, loader: 'coffeelint-loader', exclude: /node_modules/ },
],
loaders: [
// { test: /\.mustache$/, loader: 'mustache', exclude: /node_modules/ }
],
noParse: [
/node_modules/
]
},
resolve: {
extensions: ['', '.js', '.json'],
root: [path.join(__dirname, '/src/script')],
alias: {
'underscore': 'lodash'
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [ '@babel/preset-env' ]
}
}
},
{
test: /src\/style\/.+\.styl$/,
loader: 'file-loader?name=./dist/style/[name].css!css-loader!stylus-loader'
}
]
},
plugins: [
new webpack.ProvidePlugin({
// Detect and inject
_: 'underscore',
_: 'underscore'
})
],
devtool: 'inline-source-map',
debug: true
};
devtool: 'inline-source-map'
}