UNPKG

soundfics

Version:

FICS proxy which pumps up traffic by sound

285 lines (245 loc) 7.39 kB
'use strict' let fs = require('fs') let map = require('lodash.map') let random = require('lodash.random') let net = require('net') let path = require('path') let winston = require('winston') let daemonize = process.env.SOUNDFICS_DAEMONIZE const parseSpawnArg = (args) => { const all = args.split(' ') return { command: all.shift(), args: all } } let config = { fics: { host: process.env.SOUNDIFICS_FICSHOST || 'freechess.org', port: process.env.SOUNFICS_FICSPORT || '5000' }, soundfics: { listen: process.env.SOUNDFICS_LISTEN || '127.0.0.1', port: process.env.SOUNDFICS_PORT || '5000' }, backLight: process.env.SOUNDFICS_BACKLIGHT || 'true', logLevel: process.env.SOUNDFICS_LOGLEVEL || 'error', play: parseSpawnArg(process.env.SOUNDFICS_PLAY || 'aplay') } if (daemonize === undefined) { daemonize = false } if (daemonize) { require('daemon')({ cwd: process.cwd(), }) } let fics = { state: false, login: false, game: { state: false, white: false, black: false } } let transports = [] if (config.daemonize) { transports.push(new (winston.transports.File)({filename: 'soundfics.log'})) } else { transports.push(new (winston.transports.Console)({colorize: true})) } let logger = winston.createLogger({ level: config.logLevel, transports: transports }) logger.info('soundfics started') const soundBase = path.join(__dirname, 'sounds') let sounds = {} fs.readdirSync(soundBase).forEach(key => { let keyDir = path.join(soundBase, key) sounds[key] = map(fs.readdirSync(keyDir), f => `${keyDir}/${f}`) }) let spawn = require('child_process').spawn function play(file) { config.play.args.push(file) spawn(config.play.command, config.play.args) config.play.args.pop() } function getLogin(data) { let matches = /\*{4}\sStarting\sFICS session as (.+) \*{4}/.exec(data) return matches && matches.length > 1 ? matches[1] : false } function getAction(s) { // check that we logged in let login = getLogin(s) if (login) { return {id: 'login', login: login} } // check that is new game let matches = /{Game.+\((\w+)\svs.\s(\w+)\)\sCreating.+}/.exec(s) if (matches && matches.length === 3) { return {id: 'start', white: matches[1], black: matches[2]} } // check that game is over matches = /{Game.+\((\w+)\svs.\s(\w+)\).+}\s(.+)-(\d)/.exec(s) if (matches && matches.length > 4) { return { id: 'end', players: {white: matches[1], black: matches[2]}, result: {white: matches[3], black: matches[3] === '*' ? '*' : matches[4], s: s} } } // check aborted game matches = /{Game.+\((\w+)\svs.\s(\w+)\) Game aborted on move\s\d+}/.exec(s) if (matches && matches.length == 3) { return { id: 'abort', players: {white: matches[1], black: matches[2]} } } // check that is style12 matches = /(<12>)\s((\S+\s){8})([B|W])\s(-*\d)\s(\d)\s(\d)\s(\d)\s(\d)\s(\d+)\s(\d+)\s(\w+)\s(\w+)\s(-*\d)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s([BKNPQRa-ho1-8\-\/=]*)\s(\(.+\))\s([BKNPQRa-h1-8Ox+\-=]*)/.exec(s) if (matches && matches.length > 24) { return { id: 'move', board: matches[2], color: matches[4], dpp: matches[5], names: { white: matches[12], black: matches[13]}, relation: matches[15], time: {initial: matches[15], increment: matches[16]}, strength: {white: matches[17], black: matches[18]}, remainingTime: {white: matches[19], black: matches[20]}, moveNumber: matches[21], notation: {verbose: matches[22], pretty: matches[24]} } } return false } function getRandomSound(sounds, key) { let files = sounds[key] return files[random(files.length - 1)] } function action(data, sounds) { let playData = [] let s = data.toString('ascii') logger.silly('data', s) let action = getAction(s) if (action) { logger.debug(action) } if (action.id === 'login') { fics.login = action.login playData.push(getRandomSound(sounds, 'login')) } else if (action.id === 'start') { playData.push(getRandomSound(sounds, 'start')) fics.game.state = 'progress' fics.game.white = action.white fics.game.black = action.black } else if (action.id === 'move') { if (action.notation.verbose[0] === 'o') { playData.push(getRandomSound(sounds, 'castle')) } else if (action.notation.pretty.indexOf('+') !== -1) { playData.push(getRandomSound(sounds, 'check')) if (config.backLight) { playData.push(getRandomSound(sounds, 'grunt')) } } else if (action.notation.pretty.indexOf('x') !== -1) { playData.push(getRandomSound(sounds, 'capture')) if (config.backLight) { playData.push(getRandomSound(sounds, 'punch')) } } else { playData.push(getRandomSound(sounds, 'move')) } } else if (action.id === 'end') { logger.debug('fics', fics) if (action.result.white === action.result.black) { playData.push(getRandomSound(sounds, 'draw')) } else { let myColor = action.players.white === fics.login ? 'white' : 'black' if ((myColor === 'white' && parseInt(action.result.white)) || (myColor === 'black' && parseInt(action.result.black))) { playData.push(getRandomSound(sounds, 'win')) } else { playData.push(getRandomSound(sounds, 'lose')) } } fics.game = {state: false, white: false, black: false} if (fics.state === 'logout') { logout(fics) } } else if (action.id === 'abort') { playData.push(getRandomSound(sounds, 'abort')) fics.game = {white: false, black: false} } playData.forEach(file => { logger.debug('play', file) play(file) }) } function logout(fics) { logger.debug('logout called') if (!fics.game.state) { fics.game.white = false fics.game.black = false fics.login = false fics.state = false } else { fics.state = 'logout' } } let proxy = net.createServer((proxySocket) => { logger.debug('proxy socket created') let buffers = [] let ficsSocket = new net.Socket() logger.debug('before FICS connect', config.fics.host, config.fics.port) ficsSocket.connect(config.fics.port, config.fics.host, () => { buffers.forEach(buffer => { ficsSocket.write(buffer) }) }) proxySocket.on('data', data => { if (ficsSocket.writable) { ficsSocket.write(data) } else { buffers[buffers.length] = data } console.log('data', data) action(data, sounds) }) ficsSocket.on('data', data => { proxySocket.write(data) action(data, sounds) }) proxySocket.on('error', error => { logger.debug('proxy socket error event with', error) ficsSocket.end() logout(fics) }) ficsSocket.on('error', error => { logger.debug('fics socket error event with', error) proxySocket.end() logout(fics) }) proxySocket.on('close', error => { if (error) { logger.error('error on proxy connection closing', error) } else { logger.debug('proxy connection closed') } ficsSocket.end() logout(fics) }) ficsSocket.on('close', error => { if (error) { logger.error('error on fics connection closing', error) } else { logger.debug('fics connection closed') } proxySocket.end() logout(fics) }) }) proxy.listen(config.soundfics.port, config.soundfics.listen)