it doesnt build yet
This commit is contained in:
parent
73610f6c18
commit
6e39b056c3
14
.editorconfig
Normal file
14
.editorconfig
Normal 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
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/build/
|
/dist/
|
||||||
|
/lib/
|
||||||
/client.config.toml
|
/client.config.toml
|
||||||
/webirc.data.json
|
/webirc.data.json
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# TeemantIRC
|
# TeemantIRC
|
||||||
LunaSquee's third attempt at creating a working IRC client..
|
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.
|
This application requires [node.js](https://nodejs.org/) to be installed.
|
||||||
|
|
||||||
1. Install the dependencies `npm install`
|
1. Install the dependencies `npm install`
|
||||||
2. Copy the configuration `cp client.config.example.toml client.config.toml`
|
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`
|
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
|
### WebIRC
|
||||||
|
|
||||||
|
279
gulpfile.js
279
gulpfile.js
@ -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
8290
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "teemantirc",
|
"name": "teemantirc",
|
||||||
"version": "1.1.0",
|
"version": "2.0.0",
|
||||||
"description": "Web-based IRC client",
|
"description": "Web-based IRC client",
|
||||||
"main": "teemant.js",
|
"main": "teemant.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -12,38 +12,26 @@
|
|||||||
"author": "Evert",
|
"author": "Evert",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.14.0",
|
"express": "^4.16.4",
|
||||||
"socketio": "^1.0.0",
|
"socket.io": "^2.2.0",
|
||||||
"toml": "^2.3.0"
|
"toml": "^2.3.5"
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
let fs = require('fs');
|
import fs from 'fs'
|
||||||
let toml = require('toml');
|
import path from 'path'
|
||||||
let filename = __dirname+'/../client.config.toml';
|
import toml from 'toml'
|
||||||
|
|
||||||
let config;
|
const filename = path.join(__dirname, '..', 'client.config.toml')
|
||||||
|
|
||||||
|
let config
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config = toml.parse(fs.readFileSync(filename));
|
config = toml.parse(fs.readFileSync(filename))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw 'config.toml parse error: ' + e;
|
throw new Error('config.toml parse error: ', e.message)
|
||||||
console.error(e.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config
|
||||||
|
7
server/irc/index.js
Normal file
7
server/irc/index.js
Normal 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
713
server/irc/irc.js
Normal 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
62
server/irc/parser.js
Normal 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
|
||||||
|
}
|
@ -1,50 +1,52 @@
|
|||||||
const util = require('util');
|
import util from 'util'
|
||||||
const config = require(__dirname+'/config');
|
import config from './config'
|
||||||
|
|
||||||
module.exports.log = function() {
|
module.exports.log = function () {
|
||||||
console.log.apply(null, arguments);
|
console.log.apply(null, arguments)
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports.debugLog = function() {
|
module.exports.debugLog = function () {
|
||||||
if(!config.server.debug) return;
|
if (!config.server.debug) return
|
||||||
|
|
||||||
console.log.apply(null, arguments);
|
console.log.apply(null, arguments)
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports.errorLog = function(errObj, specify) {
|
module.exports.errorLog = function (errObj, specify) {
|
||||||
if(specify)
|
if (specify) console.error(specify)
|
||||||
console.error(specify);
|
console.error(errObj)
|
||||||
|
|
||||||
console.error(errObj);
|
|
||||||
|
|
||||||
if(errObj.stack)
|
if (errObj.stack) {
|
||||||
console.error(errObj.stack);
|
console.error(errObj.stack)
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.printRuntimeStats = function(runtime_stats, connections) {
|
module.exports.printRuntimeStats = function (runtimeStats, connections) {
|
||||||
if(!config.server.printStats) return;
|
if (!config.server.printStats) return
|
||||||
|
|
||||||
let date = new Date();
|
let date = new Date()
|
||||||
let users = 0;
|
let users = 0
|
||||||
let servers = 0;
|
let servers = 0
|
||||||
let serversPerUser = 0;
|
let serversPerUser = 0
|
||||||
|
|
||||||
for(let uid in connections) {
|
for (let uid in connections) {
|
||||||
let ca = connections[uid];
|
let ca = connections[uid]
|
||||||
users += 1;
|
users += 1
|
||||||
for(let snam in ca) {
|
for (let snam in ca) {
|
||||||
if(!snam) continue;
|
if (!snam) continue
|
||||||
if(snam == 'host') continue;
|
if (snam === 'host') continue
|
||||||
servers += 1;
|
servers += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(users != 0) // Don't divide by zero lmao
|
if (users !== 0) { // Don't divide by zero lmao
|
||||||
serversPerUser = servers/users;
|
serversPerUser = servers / users
|
||||||
|
}
|
||||||
|
|
||||||
console.log(date+(': Currently connected users: {0}. ' +
|
console.log(date,
|
||||||
'IRC server connections: {1}. ' +
|
util.format('- Currently connected users: %d. ' +
|
||||||
'Average servers per user: {2}. ' +
|
'IRC server connections: %d. ' +
|
||||||
'Total connections made: {3}. ' +
|
'Average servers per user: %f. ' +
|
||||||
'Uptime: {4}s;').format(users, servers, serversPerUser, runtime_stats.connectionsMade, process.uptime()));
|
'Total connections made: %d. ' +
|
||||||
};
|
'Uptime: %d s;', users, servers, serversPerUser, runtimeStats.connectionsMade, process.uptime())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
let connector = require(__dirname+'/irc.js');
|
|
||||||
let parser = require(__dirname+'/parser.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
IRCConnection: connector,
|
|
||||||
Parser: parser
|
|
||||||
};
|
|
@ -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;
|
|
@ -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;
|
|
||||||
};
|
|
178
server/webirc.js
178
server/webirc.js
@ -1,122 +1,120 @@
|
|||||||
const dns = require('dns');
|
import dns from 'dns'
|
||||||
const fs = require('fs');
|
import fs from 'fs'
|
||||||
const path = require('path');
|
import path from 'path'
|
||||||
const config = require(__dirname+'/config');
|
|
||||||
const logger = require(__dirname+'/logger');
|
|
||||||
const webirc_data_path = path.resolve(__dirname+'/../webirc.data.json');
|
|
||||||
|
|
||||||
let webirc_data = {};
|
import config from './server/config'
|
||||||
let timeouts = {};
|
import logger from './server/logger'
|
||||||
|
|
||||||
function writeToFile() {
|
const webircDataPath = path.join(__dirname, '..', 'webirc.data.json')
|
||||||
fs.writeFile(webirc_data_path, JSON.stringify(webirc_data, null, '\t'), function (err) {if (err) throw err;});
|
|
||||||
|
let webircData = {}
|
||||||
|
let timeouts = {}
|
||||||
|
|
||||||
|
function writeToFile () {
|
||||||
|
fs.writeFile(webircDataPath, JSON.stringify(webircData, null, '\t'), (err) => { if (err) throw err })
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeoutRefresh(address, seconds) {
|
function timeoutRefresh (address, seconds) {
|
||||||
if(timeouts[address])
|
if (timeouts[address]) clearTimeout(timeouts[address])
|
||||||
clearTimeout(timeouts[address]);
|
|
||||||
|
|
||||||
timeouts[address] = setTimeout(()=>{resolveAddress(address);}, seconds*1000);
|
timeouts[address] = setTimeout(() => resolveAddress(address), seconds * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAddress(address, force) {
|
function resolveAddress (address, force) {
|
||||||
logger.debugLog('** WEBIRC ** Attempting to update IP for '+address);
|
logger.debugLog('** WEBIRC ** Attempting to update IP for', address)
|
||||||
let obj = webirc_data[address];
|
let obj = webircData[address]
|
||||||
|
|
||||||
if(!obj) return;
|
|
||||||
|
|
||||||
if((Date.now() - obj.last_update)/1000 < config.webirc.resolveInterval && !force) {
|
if (!obj) return
|
||||||
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)));
|
|
||||||
|
|
||||||
return timeoutRefresh(address, nextTime);
|
if ((Date.now() - obj.last_update) / 1000 < config.webirc.resolveInterval && !force) {
|
||||||
}
|
let nextTime = (config.webirc.resolveInterval - (Date.now() - obj.last_update) / 1000)
|
||||||
|
|
||||||
new Promise((resolve, reject) => {
|
logger.debugLog(`** WEBIRC ** ${address} IP is ${obj.cachedIP}, refresh in ${Math.floor(nextTime)} seconds`)
|
||||||
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(ip) {
|
return timeoutRefresh(address, nextTime)
|
||||||
resolve(ip);
|
}
|
||||||
} else {
|
|
||||||
reject(new Error('no ips'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).then((data) => {
|
|
||||||
logger.debugLog('** WEBIRC ** Updated DNS for {0}; IP is now {1}'.format(address, data));
|
|
||||||
|
|
||||||
webirc_data[address].last_update = Date.now();
|
new Promise((resolve, reject) => {
|
||||||
webirc_data[address].cached_ip = data;
|
if (address === '127.0.0.1' || address === '0.0.0.0' || address === 'localhost') {
|
||||||
|
logger.debugLog('** WEBIRC ** Ignoring localhost entry..')
|
||||||
writeToFile();
|
return resolve('127.0.0.1')
|
||||||
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));
|
|
||||||
|
|
||||||
timeoutRefresh(address, (config.webirc.resolveInterval+60));
|
dns.resolve(address, (err, data) => {
|
||||||
});
|
if (err != null) return reject(err)
|
||||||
|
let ip = data.length > 0 ? data[0] : null
|
||||||
|
|
||||||
|
if (ip) {
|
||||||
|
resolve(ip)
|
||||||
|
} else {
|
||||||
|
reject(new Error('no ips'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).then((data) => {
|
||||||
|
logger.debugLog('** WEBIRC ** Updated DNS for {0}; IP is now {1}'.format(address, data))
|
||||||
|
|
||||||
|
webircData[address].last_update = Date.now()
|
||||||
|
webircData[address].cachedIP = data
|
||||||
|
|
||||||
|
writeToFile()
|
||||||
|
timeoutRefresh(address, config.webirc.resolveInterval)
|
||||||
|
}, (err) => {
|
||||||
|
logger.debugLog(`** WEBIRC ** Failed to updated DNS for ${address}; IP is still ${webircData[address].cachedIP}:`, err.message)
|
||||||
|
|
||||||
|
timeoutRefresh(address, config.webirc.resolveInterval + 60)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload(force) {
|
function reload (force) {
|
||||||
if(!config.webirc.enabled) return;
|
if (!config.webirc.enabled) return
|
||||||
|
|
||||||
try {
|
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]) {
|
if (require.cache && require.cache[webircDataPath]) {
|
||||||
delete require.cache[webirc_data_path];
|
delete require.cache[webircDataPath]
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
writeToFile();
|
writeToFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let adr in webirc_data) {
|
for (let adr in webircData) {
|
||||||
resolveAddress(adr, force);
|
resolveAddress(adr, force)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_password(server_ip) {
|
function getPassword (serverIP) {
|
||||||
let ip = null;
|
let ip = null
|
||||||
for(let a in webirc_data) {
|
for (let a in webircData) {
|
||||||
if(webirc_data[a].cached_ip == server_ip)
|
if (webircData[a].cachedIP !== serverIP) continue
|
||||||
ip = webirc_data[a];
|
ip = webircData[a]
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip;
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebIRCAuthenticator {
|
class WebIRCAuthenticator {
|
||||||
constructor(userInfo) {
|
constructor (userInfo) {
|
||||||
this.userInfo = userInfo;
|
this.userInfo = userInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate(connection) {
|
authenticate (connection) {
|
||||||
let serverpass = get_password(connection.config.address);
|
let serverpass = getPassword(connection.config.address)
|
||||||
if(serverpass)
|
if (serverpass) {
|
||||||
connection.socket.write('WEBIRC {0} {1} {2} {3}\r\n'.format(serverpass.password, connection.config.username,
|
connection.socket.write(`WEBIRC ${serverpass.password} ${connection.config.username} ${this.userInfo.hostname} ${this.userInfo.ipaddr}\r\n`)
|
||||||
this.userInfo.hostname, this.userInfo.ipaddr));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
reload: reload,
|
Authenticator: WebIRCAuthenticator, reload, getPassword, writeToFile
|
||||||
authenticator: WebIRCAuthenticator,
|
}
|
||||||
get_password: get_password,
|
|
||||||
writeToFile: writeToFile
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on('SIGUSR1', () => {
|
process.on('SIGUSR1', () => {
|
||||||
logger.log('\n!!! Received SIGUSR1; Reloading webirc data.. !!!\n');
|
logger.log('\n!!! Received SIGUSR1; Reloading webirc data.. !!!\n')
|
||||||
reload(true);
|
reload(true)
|
||||||
});
|
})
|
||||||
|
|
||||||
reload(false);
|
reload(false)
|
||||||
|
347
teemant.js
347
teemant.js
@ -1,217 +1,232 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict'
|
||||||
const express = require('express');
|
import express from 'express'
|
||||||
const path = require('path');
|
import path from 'path'
|
||||||
const sockio = require('socket.io');
|
import sockio from 'socket.io'
|
||||||
const dns = require('dns');
|
import dns from 'dns'
|
||||||
const app = express();
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const pubdir = path.join(__dirname, 'build');
|
import pkginfo from './package.json'
|
||||||
const pkg = require(__dirname+'/package.json');
|
import irclib from './server/irc'
|
||||||
|
import webirc from './server/webirc'
|
||||||
|
|
||||||
const config = require(__dirname+'/server/config');
|
import config from './server/config'
|
||||||
const logger = require(__dirname+'/server/logger');
|
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) {
|
let runtimeStats = {
|
||||||
String.prototype.format = function() {
|
connectionsMade: 0
|
||||||
var args = arguments;
|
|
||||||
return this.replace(/{(\d+)}/g, function(match, number) {
|
|
||||||
return typeof args[number] != undefined ? args[number] : match;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let irclib = require(__dirname+'/server/teemant_irc');
|
let connections = {}
|
||||||
let webirc = require(__dirname+'/server/webirc');
|
|
||||||
|
|
||||||
let runtime_stats = {
|
|
||||||
connectionsMade: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let connections = {};
|
|
||||||
|
|
||||||
let customCTCPs = {
|
let customCTCPs = {
|
||||||
VERSION: function(data, connection) {
|
VERSION: function (data, connection) {
|
||||||
return 'TeemantIRC ver. {0} - {1} - https://teemant.icynet.ml/'.format(pkg.version, pkg.description);
|
return `TeemantIRC ver. ${pkginfo.version} - ${pkginfo.description} - https://teemant.icynet.eu/`
|
||||||
},
|
},
|
||||||
SOURCE: function(data, connection) {
|
SOURCE: function (data, connection) {
|
||||||
return 'https://github.com/DiamondtechDev/TeemantIRC';
|
return 'https://gitlab.icynet.eu/IcyNetwork/teemant'
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
process.stdin.resume();
|
process.stdin.resume()
|
||||||
|
|
||||||
router.get('/', function(req, res){
|
router.get('/', function (req, res) {
|
||||||
res.sendFile(pubdir+'/document/index.html');
|
res.sendFile(path.join(pubdir, '/document/index.html'))
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/:server?*', function(req, res){
|
router.get('/:server?*', function (req, res) {
|
||||||
res.sendFile(pubdir+'/document/index.html');
|
res.sendFile(path.join(pubdir, '/document/index.html'))
|
||||||
});
|
})
|
||||||
|
|
||||||
app.use('/', express.static(pubdir, { maxAge: 365*24*60*60*1000 }));
|
app.use('/', express.static(pubdir, { maxAge: cacheAge }))
|
||||||
app.use('/', express.static(pubdir+'/icons', { maxAge: 365*24*60*60*1000 }));
|
app.use('/', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge }))
|
||||||
app.use('/', express.static(__dirname+'/static', { maxAge: 365*24*60*60*1000 }));
|
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, { maxAge: cacheAge }))
|
||||||
app.use('/:server', express.static(pubdir+'/icons', { maxAge: 365*24*60*60*1000 }));
|
app.use('/:server', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge }))
|
||||||
app.use('/:server', express.static(pubdir+'/static', { maxAge: 365*24*60*60*1000 }));
|
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() {
|
const io = sockio.listen(app.listen(port, function () {
|
||||||
logger.log('*** Listening on http://localhost:{0}/'.format(port));
|
logger.log(`*** Listening on http://localhost:${port}/`)
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
logger.printRuntimeStats(runtime_stats, connections);
|
logger.printRuntimeStats(runtimeStats, connections)
|
||||||
}, 3600000);
|
}, 3600000)
|
||||||
}));
|
}))
|
||||||
|
|
||||||
function resolveHostname(ipaddr) {
|
function resolveHostname (ipaddr) {
|
||||||
let promise = new Promise(function(resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
dns.reverse(ipaddr, function(err, hostnames) {
|
dns.reverse(ipaddr, function (err, hostnames) {
|
||||||
if(err != null) return reject(err);
|
if (err != null) return reject(err)
|
||||||
resolve(hostnames);
|
resolve(hostnames)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
io.sockets.on('connection', function (socket) {
|
io.sockets.on('connection', function (socket) {
|
||||||
let userip = socket.handshake.headers['x-real-ip'] || socket.handshake.headers['x-forwarded-for'] ||
|
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)
|
if (userip.indexOf('::ffff:') === 0) {
|
||||||
userip = userip.substring(7);
|
userip = userip.substring(7)
|
||||||
|
}
|
||||||
|
|
||||||
logger.debugLog('clientID: {0} from: {1}'.format(socket.id, userip));
|
logger.debugLog(`clientID: ${socket.id} from: ${userip}`)
|
||||||
|
|
||||||
socket.emit('defaults', {
|
socket.emit('defaults', {
|
||||||
server: config.client.default_server,
|
server: config.client.default_server,
|
||||||
port: config.client.default_port,
|
port: config.client.default_port,
|
||||||
ssl: config.client.secure_by_default
|
ssl: config.client.secure_by_default
|
||||||
});
|
})
|
||||||
|
|
||||||
// New object for connections
|
// New object for connections
|
||||||
connections[socket.id] = {
|
connections[socket.id] = {
|
||||||
host: {
|
host: {
|
||||||
ipaddr: userip,
|
ipaddr: userip,
|
||||||
hostname: userip
|
hostname: userip
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Get the hostname of the connecting user
|
// Get the hostname of the connecting user
|
||||||
let hostQuery = resolveHostname(userip);
|
let hostQuery = resolveHostname(userip)
|
||||||
hostQuery.then((arr) => {
|
hostQuery.then((arr) => {
|
||||||
if(arr.length > 0)
|
if (arr.length > 0) {
|
||||||
connections[socket.id].host.hostname = arr[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));
|
logger.debugLog(`Hostname of ${socket.id} was determined to be ${connections[socket.id].host.hostname}`)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
logger.debugLog('Host resolve for {0} failed: {1}'.format(socket.id, err));
|
logger.debugLog(`Host resolve for ${socket.id} failed:`, err.message)
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('disconnect', function() {
|
socket.on('disconnect', function () {
|
||||||
for (let d in connections[socket.id]) {
|
for (let d in connections[socket.id]) {
|
||||||
if(connections[socket.id][d].ipaddr) continue;
|
if (connections[socket.id][d].ipaddr) continue
|
||||||
if(connections[socket.id][d].connected == true)
|
if (connections[socket.id][d].connected === true) {
|
||||||
connections[socket.id][d].disconnect();
|
connections[socket.id][d].disconnect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete connections[socket.id];
|
delete connections[socket.id]
|
||||||
|
|
||||||
logger.debugLog('clientID: {0} disconnect'.format(socket.id));
|
logger.debugLog('clientID: {0} disconnect'.format(socket.id))
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('error', (e) => {
|
socket.on('error', (e) => {
|
||||||
logger.errorLog(e, 'Socket error');
|
logger.errorLog(e, 'Socket error')
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('userinput', (data) => {
|
socket.on('userinput', (data) => {
|
||||||
let serv = connections[socket.id][data.server];
|
let serv = connections[socket.id][data.server]
|
||||||
if(!serv) return;
|
if (!serv) return
|
||||||
if(serv.authenticated == false) 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) {
|
socket.on('irc_create', function (connectiondata) {
|
||||||
logger.debugLog(socket.id+' created irc connection: ', 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) {
|
if (connectiondata.port === null || connectiondata.server === null) {
|
||||||
return socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
|
return socket.emit('act_client', {
|
||||||
message: 'Failed to connect to the server!', error: true});
|
type: 'connect_message',
|
||||||
}
|
server: connectiondata.server,
|
||||||
|
message: 'Failed to connect to the server!',
|
||||||
|
error: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let newConnection = new irclib.IRCConnection(connectiondata, config.client,
|
let newConnection = new irclib.IRCConnection(connectiondata, config.client,
|
||||||
{
|
{
|
||||||
authenticationSteps: [
|
authenticationSteps: [
|
||||||
new webirc.authenticator(connections[socket.id].host)
|
new webirc.Authenticator(connections[socket.id].host)
|
||||||
],
|
],
|
||||||
ctcps: customCTCPs
|
ctcps: customCTCPs
|
||||||
});
|
})
|
||||||
|
|
||||||
newConnection.connect();
|
newConnection.connect()
|
||||||
|
|
||||||
connections[socket.id][connectiondata.server] = newConnection;
|
connections[socket.id][connectiondata.server] = newConnection
|
||||||
|
|
||||||
newConnection.on('authenticated', () => {
|
newConnection.on('authenticated', () => {
|
||||||
socket.emit('act_client', {type: 'event_connect', address: connectiondata.server, network: newConnection.data.network,
|
socket.emit('act_client', {
|
||||||
supportedModes: newConnection.data.supportedModes, nickname: newConnection.config.nickname,
|
type: 'event_connect',
|
||||||
max_channel_length: newConnection.data.max_channel_length});
|
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) {
|
if (config.server.debug) {
|
||||||
newConnection.on('line', function(line) {
|
newConnection.on('line', function (line) {
|
||||||
logger.debugLog('['+socket.id+'] <-', line);
|
logger.debugLog(`[${socket.id}] <-`, line)
|
||||||
});
|
})
|
||||||
|
|
||||||
newConnection.on('debug_log', function(data) {
|
newConnection.on('debug_log', function (data) {
|
||||||
logger.debugLog('['+socket.id+'] <-', data);
|
logger.debugLog(`[${socket.id}] <-`, data)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
newConnection.on('connerror', (data) => {
|
newConnection.on('connerror', (data) => {
|
||||||
logger.debugLog(data);
|
logger.debugLog(data)
|
||||||
|
|
||||||
if(newConnection.authenticated == false)
|
if (newConnection.authenticated === false) {
|
||||||
socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
|
socket.emit('act_client', {
|
||||||
message: 'Failed to connect to the server!', error: true});
|
type: 'connect_message',
|
||||||
});
|
server: connectiondata.server,
|
||||||
|
message: 'Failed to connect to the server!',
|
||||||
|
error: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
newConnection.on('pass_to_client', (data) => {
|
newConnection.on('pass_to_client', (data) => {
|
||||||
socket.emit('act_client', data);
|
socket.emit('act_client', data)
|
||||||
});
|
})
|
||||||
|
|
||||||
newConnection.on('closed', (data) => {
|
newConnection.on('closed', (data) => {
|
||||||
logger.debugLog(data);
|
logger.debugLog(data)
|
||||||
|
|
||||||
if(newConnection.authenticated == false)
|
if (newConnection.authenticated === false) {
|
||||||
socket.emit('act_client', {type: 'connect_message', server: connectiondata.server,
|
socket.emit('act_client', {
|
||||||
message: 'Failed to connect to the server!', error: true});
|
type: 'connect_message',
|
||||||
else
|
server: connectiondata.server,
|
||||||
socket.emit('act_client', {type: 'event_server_quit', 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', () => {
|
process.on('SIGINT', () => {
|
||||||
logger.log('!!! Received SIGINT; Terminating all IRC connections and exiting.. !!!');
|
logger.log('!!! Received SIGINT; Terminating all IRC connections and exiting.. !!!')
|
||||||
logger.printRuntimeStats(runtime_stats, connections);
|
logger.printRuntimeStats(runtimeStats, connections)
|
||||||
for(let c in connections) {
|
for (let c in connections) {
|
||||||
for(let ircconn in connections[c]) {
|
for (let ircconn in connections[c]) {
|
||||||
if(connections[c][ircconn].ipaddr) continue;
|
if (connections[c][ircconn].ipaddr) continue
|
||||||
connections[c][ircconn].disconnect();
|
connections[c][ircconn].disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.exit();
|
process.exit(0)
|
||||||
});
|
})
|
||||||
|
@ -1,49 +1,39 @@
|
|||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack')
|
||||||
const path = require('path');
|
const path = require('path')
|
||||||
|
|
||||||
let inProduction = process.env.NODE_ENV === 'production' || process.argv.indexOf('-p') !== -1;
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
main: ['./src/script/main']
|
main: './src/js/main'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: __dirname,
|
path: path.join(__dirname, 'dist', 'js'),
|
||||||
filename: './build/script/[name].js',
|
filename: '[name].js'
|
||||||
chunkFilename: './build/script/[id].js'
|
},
|
||||||
},
|
module: {
|
||||||
module: {
|
rules: [
|
||||||
preLoaders: [
|
{
|
||||||
// { test: /\.js$/, loader: 'eslint-loader', exclude: /node_modules/ },
|
test: /\.js$/,
|
||||||
// { test: /\.coffee$/, loader: 'coffeelint-loader', exclude: /node_modules/ },
|
exclude: /(node_modules|bower_components)/,
|
||||||
],
|
use: {
|
||||||
loaders: [
|
loader: 'babel-loader',
|
||||||
// { test: /\.mustache$/, loader: 'mustache', exclude: /node_modules/ }
|
options: {
|
||||||
],
|
presets: [ '@babel/preset-env' ]
|
||||||
noParse: [
|
}
|
||||||
/node_modules/
|
}
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
|
test: /src\/style\/.+\.styl$/,
|
||||||
resolve: {
|
loader: 'file-loader?name=./dist/style/[name].css!css-loader!stylus-loader'
|
||||||
extensions: ['', '.js', '.json'],
|
}
|
||||||
root: [path.join(__dirname, '/src/script')],
|
]
|
||||||
|
},
|
||||||
alias: {
|
plugins: [
|
||||||
'underscore': 'lodash'
|
new webpack.ProvidePlugin({
|
||||||
}
|
// Detect and inject
|
||||||
},
|
_: 'underscore'
|
||||||
|
})
|
||||||
plugins: [
|
],
|
||||||
|
devtool: 'inline-source-map'
|
||||||
new webpack.ProvidePlugin({
|
}
|
||||||
// Detect and inject
|
|
||||||
_: 'underscore',
|
|
||||||
})
|
|
||||||
],
|
|
||||||
|
|
||||||
devtool: 'inline-source-map',
|
|
||||||
debug: true
|
|
||||||
};
|
|
||||||
|
Reference in New Issue
Block a user