Commit 9b2c2320 authored by Kasalehlia's avatar Kasalehlia

Merge branch 'master' into 'master'

future! (as in not EOL) node, flex design

See merge request !4
parents d9db3ca6 92a6a8c3
{
"presets": ["es2015"]
}
module.exports = {
extends: 'marudor/noReact',
env: {
node: true,
},
globals: {
pad: false,
DOW: false,
},
rules: {
'header/header': 0,
}
}
*.swp
npm-debug.log
node_modules
modules
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(8000);
app.use(express.static(__dirname + '/public'));
var instanceClients = [];
io.on('connection', function (socket) {
socket.on('ident', function (client) {
if (instanceClients.indexOf(client) === -1) {
instanceClients.push(client);
socket.emit('meta', 'reload');
}
})
});
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
// for reasons
DOW = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
pad = function (n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
var normalizedPath = require("path").join(__dirname, "modules");
require("fs").readdirSync(normalizedPath).forEach(function(file) {
if (endsWith(file,'.js')) {
try {
require("./modules/" + file)(io);
console.log(file+" loaded");
} catch (e) {
console.log("Error loading "+file);
console.log(e);
}
}
});
require('./modules/main.js');
var httpreq = require('httpreq');
var Mustache = require('mustache');
var URL = 'https://stratum0.org/status/status.json';
var TEMPLATES = {};
require('fs').readFile('modules/brand/template.mustache', 'utf-8', function (err, data) {
TEMPLATES.template = data;
});
require('fs').readFile('modules/brand/status.mustache', 'utf-8', function (err, data) {
TEMPLATES.status = data;
});
var status = {};
function renderStatus(sock, everything) {
var sendInner = function () {
status.random = ''+Math.random();
sock.emit('brand.status', Mustache.render(TEMPLATES.status, status));
}
if (everything) {
sock.emit('brand', Mustache.render(TEMPLATES.template, {}));
setTimeout(sendInner, 3000);
} else {
sendInner();
}
}
function fetchStatus(cb) {
httpreq.get(URL, function (err, res) {
var state = JSON.parse(res.body).state;
cb(state);
});
}
module.exports = function (io) {
var firstTime = true;
function update() {
fetchStatus(function (state) {
if (status.lastchange != state.lastchange) {
var d = new Date(state.lastchange*1000);
state.since = DOW[d.getDay()]+', '+pad(d.getHours(),2)+':'
+pad(d.getMinutes(),2);
status = state;
renderStatus(io);
}
if (firstTime && status) {
io.on('connection', function (sock) {
renderStatus(sock, true);
});
renderStatus(io, true);
firstTime = false;
} else {
renderStatus(io, false);
}
});
}
setInterval(update, 2*60*1000); //every 2 minutes
update();
}
var fs = require('fs');
var Mustache = require('mustache');
var XML = require('pixl-xml');
var httpreq = require('httpreq');
/// CONFIG
var CITY = 'Braunschweig';
var STOPS = ['Ludwigstraße','Hamburger Straße'];
var ENTRIES = 6;
/// VARS
var CACHED = [];
var TEMPLATES = {};
fs.readFile('modules/bus/outer.mustache', 'utf-8', function (err, data) {
TEMPLATES.outer = data;
});
fs.readFile('modules/bus/inner.mustache', 'utf-8', function (err, data) {
TEMPLATES.inner = data;
});
/// FUNS
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function fetchData(stop, cb) {
var url = "http://62.154.206.87/efaws2/default/XML_DM_REQUEST?sessionID=0&requestID=0&language=de&useRealtime=1&coordOutputFormat=WGS84[DD.ddddd]&locationServerActive=1&mode=direct&dmLineSelectionAll=1&depType=STOPEVENTS&useAllStops=1&command=null&type_dm=stop&name_dm="+CITY+' '+stop+"&mId=efa_rc2"
httpreq.get(url, {binary: true}, function (err, res) {
try {
cb(XML.parse(res.body));
} catch(e) {
}
});
}
function renderLine(line) {
var image = '';
if (line.charAt(0) == "M") {
image = 'metro.svg';
line = line.substr(1);
} else if (line.length < 3) {
image = 'tram.svg';
} else {
image = 'bus.svg';
}
return '<img src="/modules/bus/'+image+'"> '+line;
}
function getData(stop, count, cb) {
fetchData(stop, function (data) {
var deps = data.itdDepartureMonitorRequest.itdDepartureList.itdDeparture;
cb(deps.slice(0, count).map(function (dep) {
return {
line: dep.itdServingLine.symbol,
renderedLine: renderLine(dep.itdServingLine.symbol),
dir: dep.itdServingLine.direction.replace(CITY, '').trim(),
platform: dep.platform,
mean: dep.itdServingLine.itdNoTrain.name,
day: pad(dep.itdDateTime.itdDate.day,2),
month: pad(dep.itdDateTime.itdDate.month,2),
hour: pad(dep.itdDateTime.itdTime.hour,2),
minute: pad(dep.itdDateTime.itdTime.minute,2)
};
}));
});
}
function update(io, allStopsDoneCb) {
var done = [];
var context = [];
var innerGetData = function (stop, i) {
var ns = normalizeStop(stop);
getData(stop, ENTRIES, function (deps) {
try {
context[i] = {stop: stop, normalizedStop: ns, deps: deps};
io.emit('bus.'+ns, Mustache.render(TEMPLATES.inner, context[i]));
if (done.indexOf(stop) === -1) {
done.push(stop);
if (done.length === STOPS.length) {
allStopsDoneCb();
}
}
CACHED = context;
} catch (e) {console.log(e);}
// calculate when to update next
var d = new Date();
var hour = d.getHours();
var minute = d.getMinutes();
var depHour = deps[0].hour < hour ? deps[0].hour+24 : deps[0].hour;
var diff = (depHour-hour)*60 + (deps[0].minute-minute);
setTimeout(function () {
innerGetData(stop, i);
}, (diff * 60 + (60-d.getSeconds()) % 60) * 1000);
});
}
STOPS.forEach(function (stop, i) {
innerGetData(stop, i);
});
}
function normalizeStop(stop) {
return stop.replace(/[^a-zA-Z0-9_]/g,'');
}
module.exports = function (io) {
update(io, function () {
var pushToClient = function (sock) {
sock.emit('bus', Mustache.render(TEMPLATES.outer, CACHED));
setTimeout(function () {
for (var i in CACHED) {
sock.emit('bus.'+CACHED[i].normalizedStop,
Mustache.render(TEMPLATES.inner, CACHED[i]));
}
}, 3000); //wait a second to let the client process the outlets
};
io.on('connect', pushToClient);
pushToClient(io);
});
}
var httpreq = require('httpreq');
var ical = require('ical.js');
var Mustache = require('mustache');
var time = require('time');
var URL = 'https://stratum0.org/kalender/termine.ics';
var TEMPLATE = '';
require('fs').readFile('modules/calendar/template.mustache', 'utf-8', function (err, data) {
TEMPLATE = data;
});
var CALENDAR;
var TZOFFSET = new time.Date().getTimezoneOffset()*60;
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function parseDate(s) {
var i = parseInt;
if (s.indexOf('T') > -1) {
return new Date(i(s.substr(0,4)), i(s.substr(4,2))-1, i(s.substr(6,2)),
i(s.substr(9,2)), i(s.substr(11,2)), i(s.substr(13,2)));
} else {
return new Date(i(s.substr(0,4)), i(s.substr(4,2))-1, i(s.substr(6,2)));
}
}
function getData(count, cb) {
httpreq.get(URL, function (err, res) {
var ics = res.body.replace(/[0-9]{8}T[0-9]{6}/g, '$&Z');
var cal = new ical.Component(ical.parse(ics));
var rawEvents = cal.getAllSubcomponents('vevent').map(function (raw) {
return new ical.Event(raw);
});
var events = [];
var ev, p = function (p) {return ev.component.getFirstPropertyValue(p);};
for (var i in rawEvents) {
ev = rawEvents[i];
if (ev.isRecurring()) {
var iter = ev.iterator();
var duration = p('dtend').toUnixTime()-p('dtstart').toUnixTime();
for (var i = 0; i < 100; i++) {
var next = iter.next();
if (next === undefined) {break;}
var start = next.toUnixTime()+TZOFFSET;
events.push({
title: p('summary'),
start: start,
end: start + duration
});
}
} else {
events.push({
title: p('summary'),
start: p('dtstart').toUnixTime()+TZOFFSET,
end: p('dtend').toUnixTime()+TZOFFSET,
});
}
}
events.sort(function (a, b) {
return a.start - b.start;
});
var threshold = Date.now()/1000 - 5*60*60; //show 5 hours ago
var i;
for (i in events) {
if (events[i].end > threshold) {
i = parseInt(i);
break;
}
}
var now = new Date();
cb(events.slice(i, i+count).map(function (ev) {
var start = new Date(ev.start*1000);
if (start.getMonth() == now.getMonth() && start.getDate() == now.getDate()) {
ev.startRendered = pad(start.getHours(),2)+':'+pad(start.getMinutes(),2);
} else {
ev.startRendered = DOW[start.getDay()]+', '+pad(start.getDate(),2)
+'.'+pad(start.getMonth()+1,2)+'. '+pad(start.getHours(),2)
+':'+pad(start.getMinutes(),2);
}
var end = new Date(ev.end*1000);
if (ev.end - ev.start >= 60*60*24) {
ev.endRendered = DOW[end.getDay()]+' '+pad(end.getHours(),2)
+':'+pad(end.getMinutes(),2);
} else {
ev.endRendered = pad(end.getHours(),2)+':'+pad(end.getMinutes(),2);
}
var dur = Math.floor((ev.end-ev.start)/60);
ev.duration = pad(Math.floor(dur/60),2)+':'+pad((dur%60),2)
if (start.getTime() < now.getTime()) {
if (end.getTime() < now.getTime()) {
ev.past = true;
} else {
ev.now = true;
}
}
return ev
}));
});
}
module.exports = function (io) {
function update(firstRunCallback) {
getData(8, function (data) {
CALENDAR = data;
io.emit('calendar', Mustache.render(TEMPLATE, CALENDAR));
if (firstRunCallback) {
firstRunCallback()
firstRunCallback = null;
}
});
}
update(function () {
var pushToClients = function (sock) {
sock.emit('calendar', Mustache.render(TEMPLATE, CALENDAR));
};
io.on('connect', pushToClients);
pushToClients(io);
});
setInterval(update, 600000);
}
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function date() {
var d = new Date();
return DOW[d.getDay()]+', '+
pad(d.getDate(),2)+'.'+pad(d.getMonth()+1,2)+'.'+d.getFullYear()+' '+
pad(d.getHours(),2)+':'+pad(d.getMinutes(),2)+':'+pad(d.getSeconds(),2);
}
module.exports = function (io) {
setInterval(function () {
io.emit('date', '<div align="right"><h2>'+date()+'&nbsp;</h2></div>');
}, 1000);
}
var Mustache = require('mustache');
var httpreq = require('httpreq');
var TEMPLATES = {main: ''};
require('fs').readFile('modules/diagrams/main.mustache', 'utf-8', function (err, data) {
TEMPLATES.main = data;
});
require('fs').readFile('modules/diagrams/data.mustache', 'utf-8', function (err, data) {
TEMPLATES.data = data;
});
function fetchData(id, cb) {
var url = "http://shiny.tinyhost.de/php/getdata.php?time=1&id[]="+id;
httpreq.get(url, {binary: true}, function (err, res) {
try {
cb(res.body.toString().split('\n'));
} catch(e) {}
});
}
DATA = {power: '[]', devices: '[]'};
function handleData(prop, data) {
var line,out = []
for (var i = 1; i < data.length-1; i++) {
line = data[i].split(',');
if (line.length === 2) {
out.push([ (new Date(line[0])).getTime() , parseFloat(line[1]) ])
}
}
DATA[prop] = JSON.stringify(out);
}
function update(cb) {
var counter = 0;
var next = function () {
if (++counter === 2) {
cb();
}
}
fetchData(1, function (data) {
handleData('power', data);
next();
}); //power
fetchData(4, function (data) {
handleData('devices', data);
next();
}); //devices
}
module.exports = function (io) {
setInterval(function () {
update(function () {
io.emit('diagrams.data', Mustache.render(TEMPLATES.data, DATA));
});
}, 5000);
io.on('connect', function (sock) {
sock.emit('diagrams', Mustache.render(TEMPLATES.main, {}));
});
}
var irc = require('irc');
var CHANNEL = '#stratum0';
var PW = 'Fagee9ie'
module.exports = function (io) {
var client = new irc.Client('bouncer.ksal.de', 'infodisplay', {
channels: [CHANNEL],
port: 28921,
secure: true,
selfSigned: true,
userName: 'infodisplay/Freenode',
password: PW
});
var content = [];
client.addListener('message', function (from, to, message) {
if (to != CHANNEL || from === undefined) {return;}
message = message.replace(/</g,'&lt;').replace(/>/g,'&gt;');
content.push('<p><span>'+from+'</span> '+message+'</p>');
if (content.length > 25) {
content.shift();
}
io.emit('irc.inner', content.join(''));
});
io.on('connect', function (sock) {
sock.emit('irc', '<h3>&nbsp;IRC #stratum0</h3><div class="chat" data-infodisplay-outlet="inner"></div>');
setTimeout(function () {
sock.emit('irc.inner', content.join(''));
}, 3000);
});
}
var httpreq = require('httpreq');
var fs = require('fs');
var Mustache = require('mustache');
///CONFIG
var APPID = "fdc3690e6f9a7572128fe4012b4a2500"
var CITYID = "2945024"
/// STATICS
var directions = {NNE:11.25,NE:33.75,ENE:56.25,E:78.75,ESE:101.25,SE:123.75,SSE:146.25,S:168.75,SSW:191.25,SW:213.75,WSW:236.25,W:258.75,WNW:281.25,NW:303.75,NNW:326.25,N:348.75}
var iconBaseURL = 'http://openweathermap.org/img/w/';
var TEMPLATE = '';
fs.readFile('modules/weather/template.mustache', 'utf-8', function (err, data) {
TEMPLATE = data;
});
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function fetchCurrent (cityid, cb) {
var url = "http://api.openweathermap.org/data/2.5/weather?units=metric&id="+cityid+"&appid="+APPID;
httpreq.get(url, function (err, res) {
var dat = JSON.parse(res.body);
if (dat.cod) {
cb({
temp: dat.main.temp,
wind: {speed: dat.wind.speed, dir: degToDirection(dat.wind.deg)},
pressure: dat.main.pressure,
humidity: dat.main.humidity,
main: dat.weather[0].main,
desc: dat.weather[0].description,
icon: iconBaseURL+dat.weather[0].icon+'.png'
});
}
});
}
function fetchForecast (cityid, count, cb) {
var url = "http://api.openweathermap.org/data/2.5/forecast?units=metric&id="+cityid+"&appid="+APPID;
httpreq.get(url, function (err, res) {
var raw = JSON.parse(res.body);
if (raw.list) {
var dat = raw.list.slice(0,count);
cb(dat.map(function (d) {
var date = new Date(d.dt*1000);
return {
time: pad(date.getHours(),2)+':'+pad(date.getMinutes(),2),
temp: d.main.temp,
wind: {speed: d.wind.speed, dir: degToDirection(d.wind.deg)},
main: d.weather[0].main,
desc: d.weather[0].description,
icon: iconBaseURL+d.weather[0].icon+'.png'
}
}));
}
});
}
function degToDirection(deg) {
var dir = 'N';
for (i in directions) {
if (deg > directions[i]) {
dir = i;
}
}
return dir;
}
module.exports = function (io) {
var context = {};
var firstTime = true;
var update = function (firstUpdateCb) {
var then = function () {
if (context.current && context.forecast && firstTime) {
firstUpdateCb();
firstTime = false;
}
};
fetchCurrent(CITYID, function (current) {
context.current = current;
then();
});
fetchForecast(CITYID, 6, function (forecast) {
context.forecast = forecast;
then();
});
};
update(function () {
var pushToClients = function (sock) {
sock.emit('weather', Mustache.render(TEMPLATE, context));
};
io.on('connect', pushToClients);
pushToClients(io);
});
setInterval(update, 10*60*1000);
}
......@@ -4,19 +4,32 @@
"description": "",
"main": "main.js",
"dependencies": {
"babel-polyfill": "^6.23.0",
"ee-first": "^1.1.1",
"express": "^4.13.3",
"httpreq": "^0.4.13",
"ical.js": "^1.1.2",
"irc": "^0.4.0",
"irc": "^0.5.2",
"ms": "^0.7.1",
"mustache": "^2.2.0",
"pixl-xml": "^1.0.4",
"socket.io": "^1.3.7",
"time": "^0.11.4"
},
"devDependencies": {},
"devDependencies": {
"babel": "^6.23.0",
"babel-cli": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-node6": "^11.0.0",
"eslint": "^3.15.0",
"eslint-config-marudor": "^4.1.2",
"nodemon": "^1.11.0"
},
"scripts": {
"build": "babel src --out-dir modules --copy-files --source-maps",
"dev": "nodemon --watch modules --exec 'node main.js'",
"watch": "npm run build -- --watch",
"start": "node main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Kasalehlia",
......
html {
margin: 0px;
height: 100vh;
margin: 0px;
height: 100vh;
}
body {
margin: 0px;
padding: 0px;
max-width: 100%;
border: none;
height: 100vh;
display: flex;
flex-direction: column;
margin: 0px;
padding: 0px;
max-width: 100%;
border: none;
height: 100vh;
}
body > div {
position: fixed;
#border: 1px solid;
overflow: hidden;
#border: 1px solid;
overflow: hidden;
}
body > div > div {
overflow: hidden;
}
#irc p {
margin: 0px;
line-height: 120%;
padding-left: 1em;
margin: 0px;
line-height: 120%;
padding-left: 1em;
}
h2 {
margin: 0px;
margin: 0px;
}
h3 {
margin: 0px;
margin: 0px;
}
#irc {
font-size: 120%;
font-size: 120%;
}
#irc .chat {
display: flex;
justify-content: flex-end;
flex-direction: column;
height: calc(100% - 46px);
font-family: monospace;
overflow: hidden;
display: flex;
justify-content: flex-end;
flex-direction: column;
height: calc(100% - 46px);
font-family: monospace;
overflow: hidden;
}
#irc p span {