Add contributors file
Add irc.js for peering with IRC Change default to reflect that Add basic peering. Implemented: register/unregister a user messages join/part Not implemented/TODO: Kill, kick, change nick It is likely that the web chat will not have full feature parity with clients connecting directly to IRC.merge-requests/1/head
parent
182b7e2f5f
commit
9076b1817b
|
@ -0,0 +1,2 @@
|
||||||
|
knotteye <knotteye@airmail.cc>
|
||||||
|
crushv <nik@telekem.net>
|
|
@ -12,9 +12,11 @@ rootredirect = '/users/live'
|
||||||
|
|
||||||
[ircd]
|
[ircd]
|
||||||
enable = false
|
enable = false
|
||||||
port = 7000
|
port = 6667
|
||||||
user = ''
|
sid = ''
|
||||||
|
server = ''
|
||||||
pass = ''
|
pass = ''
|
||||||
|
vhost = 'web.satyr.net'
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
host = 'localhost'
|
host = 'localhost'
|
||||||
|
|
|
@ -56,7 +56,7 @@ async function run() {
|
||||||
db.init(dbcfg, bcryptcfg);
|
db.init(dbcfg, bcryptcfg);
|
||||||
await cleanup.init(config.server.http.directory);
|
await cleanup.init(config.server.http.directory);
|
||||||
api.init(satyr);
|
api.init(satyr);
|
||||||
http.init(satyr, config.server.http.port);
|
http.init(satyr, config.server.http.port, config.ircd);
|
||||||
mediaserver.init(nms, satyr);
|
mediaserver.init(nms, satyr);
|
||||||
}
|
}
|
||||||
run();
|
run();
|
||||||
|
|
22
src/http.ts
22
src/http.ts
|
@ -7,6 +7,7 @@ import * as http from "http";
|
||||||
import * as dirty from "dirty";
|
import * as dirty from "dirty";
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import * as db from "./database";
|
import * as db from "./database";
|
||||||
|
import * as irc from "./irc";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
@ -14,7 +15,7 @@ const io = socketio(server);
|
||||||
const store = dirty();
|
const store = dirty();
|
||||||
var njkconf;
|
var njkconf;
|
||||||
|
|
||||||
function init(satyr: any, port: number){
|
async function init(satyr: any, port: number, ircconf: any){
|
||||||
njk.configure('templates', {
|
njk.configure('templates', {
|
||||||
autoescape: true,
|
autoescape: true,
|
||||||
express : app,
|
express : app,
|
||||||
|
@ -119,26 +120,44 @@ function init(satyr: any, port: number){
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
res.status(404).render('404.njk', njkconf);
|
res.status(404).render('404.njk', njkconf);
|
||||||
});
|
});
|
||||||
|
//irc peering
|
||||||
|
if(ircconf.enable){
|
||||||
|
await irc.connect({
|
||||||
|
port: ircconf.port,
|
||||||
|
sid: ircconf.sid,
|
||||||
|
pass: ircconf.pass,
|
||||||
|
server: ircconf.server,
|
||||||
|
vhost: ircconf.vhost
|
||||||
|
});
|
||||||
|
irc.events.on('message', (nick, channel, msg) => {
|
||||||
|
io.to(channel).emit('MSG', {nick: nick, msg: msg});
|
||||||
|
});
|
||||||
|
}
|
||||||
//socket.io chat logic
|
//socket.io chat logic
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
socket.nick = await newNick(socket);
|
socket.nick = await newNick(socket);
|
||||||
|
if(ircconf.enable) irc.registerUser(socket.nick);
|
||||||
socket.on('JOINROOM', async (data) => {
|
socket.on('JOINROOM', async (data) => {
|
||||||
let t: any = await db.query('select username from users where username='+db.raw.escape(data));
|
let t: any = await db.query('select username from users where username='+db.raw.escape(data));
|
||||||
if(t[0]){
|
if(t[0]){
|
||||||
socket.join(data);
|
socket.join(data);
|
||||||
io.to(data).emit('JOINED', {nick: socket.nick});
|
io.to(data).emit('JOINED', {nick: socket.nick});
|
||||||
|
if(ircconf.enable) irc.join(socket.nick, data);
|
||||||
}
|
}
|
||||||
else socket.emit('ALERT', 'Room does not exist');
|
else socket.emit('ALERT', 'Room does not exist');
|
||||||
});
|
});
|
||||||
socket.on('LEAVEROOM', (data) => {
|
socket.on('LEAVEROOM', (data) => {
|
||||||
socket.leave(data);
|
socket.leave(data);
|
||||||
|
if(ircconf.enable) irc.part(socket.nick, data);
|
||||||
io.to(data).emit('LEFT', {nick: socket.nick});
|
io.to(data).emit('LEFT', {nick: socket.nick});
|
||||||
});
|
});
|
||||||
socket.on('disconnecting', (reason) => {
|
socket.on('disconnecting', (reason) => {
|
||||||
let rooms = Object.keys(socket.rooms);
|
let rooms = Object.keys(socket.rooms);
|
||||||
for(let i=1;i<rooms.length;i++){
|
for(let i=1;i<rooms.length;i++){
|
||||||
|
if(ircconf.enable) irc.part(socket.nick, rooms[i]);
|
||||||
io.to(rooms[i]).emit('ALERT', socket.nick+' disconnected');
|
io.to(rooms[i]).emit('ALERT', socket.nick+' disconnected');
|
||||||
}
|
}
|
||||||
|
irc.unregisterUser(socket.nick);
|
||||||
store.rm(socket.nick);
|
store.rm(socket.nick);
|
||||||
});
|
});
|
||||||
socket.on('NICK', async (data) => {
|
socket.on('NICK', async (data) => {
|
||||||
|
@ -164,6 +183,7 @@ function init(satyr: any, port: number){
|
||||||
});
|
});
|
||||||
socket.on('MSG', (data) => {
|
socket.on('MSG', (data) => {
|
||||||
io.to(data.room).emit('MSG', {nick: socket.nick, msg: data.msg});
|
io.to(data.room).emit('MSG', {nick: socket.nick, msg: data.msg});
|
||||||
|
if(ircconf.enable) irc.send(socket.nick, data.room, data.msg);
|
||||||
});
|
});
|
||||||
socket.on('KICK', (data) => {
|
socket.on('KICK', (data) => {
|
||||||
if(socket.nick === data.room){
|
if(socket.nick === data.room){
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
// written by crushv <nik@telekem.net>
|
||||||
|
// thanks nikki
|
||||||
|
|
||||||
|
const net = require('net')
|
||||||
|
const EventEmitter = require('events')
|
||||||
|
|
||||||
|
const socket = new net.Socket()
|
||||||
|
const emitter = new EventEmitter()
|
||||||
|
|
||||||
|
socket.setEncoding('utf8')
|
||||||
|
|
||||||
|
socket.on('error', console.error)
|
||||||
|
|
||||||
|
function m (text) {
|
||||||
|
console.log('> ' + text)
|
||||||
|
socket.write(text + '\r\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
var config
|
||||||
|
|
||||||
|
socket.once('connect', async () => {
|
||||||
|
console.log('Connected')
|
||||||
|
m(`PASS ${config.pass} TS 6 :${config.sid}`)
|
||||||
|
m('CAPAB QS ENCAP EX IE SAVE EUID')
|
||||||
|
m(`SERVER ${config.server} 1 satyr`)
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseLine (l) {
|
||||||
|
const colIndex = l.lastIndexOf(':')
|
||||||
|
if (colIndex > -1) {
|
||||||
|
return {
|
||||||
|
params: l.substring(0, colIndex - 1).split(' '),
|
||||||
|
query: l.substring(colIndex + 1)
|
||||||
|
}
|
||||||
|
} else return { params: l.split(' ') }
|
||||||
|
}
|
||||||
|
|
||||||
|
const servers = []
|
||||||
|
const users = {}
|
||||||
|
const channels = {}
|
||||||
|
|
||||||
|
const globalCommands = {
|
||||||
|
// PING :42X
|
||||||
|
// params: SID
|
||||||
|
PING: l => {
|
||||||
|
const { query } = parseLine(l)
|
||||||
|
m(`PONG :${query}`)
|
||||||
|
emitter.emit('ping')
|
||||||
|
},
|
||||||
|
// PASS hunter2 TS 6 :42X
|
||||||
|
// params: password, 'TS', TS version, SID
|
||||||
|
PASS: l => {
|
||||||
|
const { query } = parseLine(l)
|
||||||
|
// adds a server
|
||||||
|
servers.push(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverCommands = {
|
||||||
|
// EUID nik 1 1569146316 +i ~nik localhost6.attlocal.net 0::1 42XAAAAAB * * :nik
|
||||||
|
// params: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address, UID, real hostname, account name, gecos
|
||||||
|
EUID: l => {
|
||||||
|
const { params } = parseLine(l)
|
||||||
|
const user = {
|
||||||
|
nick: params[0],
|
||||||
|
nickTS: params[2],
|
||||||
|
modes: params[3],
|
||||||
|
username: params[4],
|
||||||
|
vhost: params[5],
|
||||||
|
ip: params[6],
|
||||||
|
uid: params[7]
|
||||||
|
}
|
||||||
|
users[user.uid] = user
|
||||||
|
},
|
||||||
|
// SJOIN 1569142987 #test +nt :42XAAAAAB
|
||||||
|
// params: channelTS, channel, simple modes, opt. mode parameters..., nicklist
|
||||||
|
SJOIN: l => {
|
||||||
|
const { params, query } = parseLine(l)
|
||||||
|
const channel = {
|
||||||
|
timestamp: params[0],
|
||||||
|
name: params[1],
|
||||||
|
modes: params.slice(2).join(' '),
|
||||||
|
nicklist: query.split(' ').map(uid => {
|
||||||
|
if (/[^0-9a-zA-Z]/.test(uid[0])) return { uid: uid.slice(1), mode: uid[0] }
|
||||||
|
else return { uid: uid, mode: '' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
channels[channel.name] = channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userCommands = {
|
||||||
|
// :42XAAAAAC PRIVMSG #test :asd
|
||||||
|
// params: target, msg
|
||||||
|
PRIVMSG: (l, source) => {
|
||||||
|
const { params, query } = parseLine(l)
|
||||||
|
emitter.emit('message', users[source].nick, params[0], query)
|
||||||
|
},
|
||||||
|
// :42XAAAAAC JOIN 1569149395 #test +
|
||||||
|
JOIN: (l, source) => {
|
||||||
|
const { params } = parseLine(l)
|
||||||
|
channels[params[1]].nicklist.push({
|
||||||
|
uid: source
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// :42XAAAAAC PART #test :WeeChat 2.6
|
||||||
|
PART: (l, source) => {
|
||||||
|
const { params } = parseLine(l)
|
||||||
|
for (let i = 0; i < channels[params[0]].nicklist.length; i++) {
|
||||||
|
if (channels[params[0]].nicklist[i].uid === source) {
|
||||||
|
channels[params[0]].nicklist.splice(i, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QUIT: (_l, source) => {
|
||||||
|
delete users[source]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parser (l) {
|
||||||
|
const split = l.split(' ')
|
||||||
|
const cmd = split[0]
|
||||||
|
const args = split.slice(1).join(' ')
|
||||||
|
if (globalCommands[cmd]) return globalCommands[cmd](args)
|
||||||
|
if (cmd[0] === ':') {
|
||||||
|
const source = cmd.slice(1)
|
||||||
|
const subcmd = split[1]
|
||||||
|
const subargs = split.slice(2).join(' ')
|
||||||
|
if (servers.indexOf(source) > -1 && serverCommands[subcmd]) serverCommands[subcmd](subargs)
|
||||||
|
if (users[source] && userCommands[subcmd]) userCommands[subcmd](subargs, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('data', data => {
|
||||||
|
data.split('\r\n')
|
||||||
|
.filter(l => l !== '')
|
||||||
|
.forEach(l => {
|
||||||
|
console.log('< ' + l)
|
||||||
|
parser(l)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
socket.write('QUIT\r\n')
|
||||||
|
process.exit()
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports.connect = conf => new Promise((resolve, reject) => {
|
||||||
|
emitter.once('ping', resolve)
|
||||||
|
config = conf
|
||||||
|
socket.connect(config.port)
|
||||||
|
})
|
||||||
|
module.exports.events = emitter
|
||||||
|
|
||||||
|
const genTS = () => Math.trunc((new Date()).getTime() / 1000)
|
||||||
|
const genUID = () => {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
var uid = ''
|
||||||
|
for (let i = 0; i < 6; i++) uid += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
if (users[uid]) return genUID()
|
||||||
|
return config.sid + uid
|
||||||
|
}
|
||||||
|
const getUID = nick => {
|
||||||
|
for (const key in users) if (users[key].nick === nick) return key
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.registerUser = nick => {
|
||||||
|
const user = {
|
||||||
|
nick: nick,
|
||||||
|
nickTS: genTS(),
|
||||||
|
modes: '+i',
|
||||||
|
username: '~' + nick,
|
||||||
|
vhost: config.vhost,
|
||||||
|
ip: '0::1',
|
||||||
|
uid: genUID()
|
||||||
|
}
|
||||||
|
users[user.uid] = user
|
||||||
|
m(`EUID ${user.nick} 1 ${user.nickTS} ${user.modes} ~${user.nick} ${user.vhost} 0::1 ${user.uid} * * :${user.nick}`)
|
||||||
|
}
|
||||||
|
module.exports.unregisterUser = nick => {
|
||||||
|
const uid = getUID(nick)
|
||||||
|
m(`:${uid} QUIT :Quit: satyr`)
|
||||||
|
delete users[uid]
|
||||||
|
}
|
||||||
|
module.exports.join = (nick, channelName) => {
|
||||||
|
const uid = getUID(nick)
|
||||||
|
if (!channels[channelName]) {
|
||||||
|
const channel = {
|
||||||
|
timestamp: genTS(),
|
||||||
|
name: channelName,
|
||||||
|
modes: '+nt',
|
||||||
|
nicklist: [{ uid: uid, mode: '' }]
|
||||||
|
}
|
||||||
|
channels[channel.name] = channel
|
||||||
|
}
|
||||||
|
m(`:${uid} JOIN ${channels[channelName].timestamp} ${channelName} +`)
|
||||||
|
}
|
||||||
|
module.exports.part = (nick, channelName) => {
|
||||||
|
const uid = getUID(nick)
|
||||||
|
m(`:${uid} PART ${channelName} :satyr`)
|
||||||
|
for (let i = 0; i < channels[channelName].nicklist.length; i++) {
|
||||||
|
if (channels[channelName].nicklist[i].uid === uid) {
|
||||||
|
channels[channelName].nicklist.splice(i, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports.send = (nick, channelName, message) => {
|
||||||
|
const uid = getUID(nick)
|
||||||
|
m(`:${uid} PRIVMSG ${channelName} :${message}`)
|
||||||
|
emitter.emit('message', nick, channelName, message)
|
||||||
|
}
|
Reference in New Issue