summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2016-09-13 11:43:06 +0200
committertomsmeding <tom.smeding@gmail.com>2016-09-13 11:44:11 +0200
commitf00ba92ed2cc1a9c24ad783e83525d1b5a85b857 (patch)
tree4d1c00a47c7f3842bcf3dece83d3c00ed3ae459f
Initial
-rw-r--r--.gitignore4
-rw-r--r--index.html29
-rw-r--r--modules/$common.js33
-rwxr-xr-xmodules/abbrgen/abbreviation_gen_Darwinbin0 -> 30896 bytes
-rwxr-xr-xmodules/abbrgen/abbreviation_gen_Linuxbin0 -> 19364 bytes
-rw-r--r--modules/abbrgen/abbrgen.js49
-rw-r--r--modules/changes/changes.html257
-rw-r--r--modules/changes/changes.js163
-rw-r--r--modules/datumpjeprik/datumpjeprik.html168
-rw-r--r--modules/datumpjeprik/datumpjeprik.js66
-rw-r--r--modules/ip/ip.js8
-rw-r--r--modules/nlen/English-Dutch-mistranslations.json31
-rw-r--r--modules/nlen/nlen.html11
-rw-r--r--modules/nlen/nlen.js17
-rw-r--r--modules/poke/poke.html125
-rw-r--r--modules/poke/poke.js47
-rw-r--r--modules/quotesgrab/quotesgrab.js65
-rw-r--r--modules/subd-krokodil/subd-krokodil.js21
-rwxr-xr-xmodules/ytdl/youtube-dl-Darwinbin0 -> 1149466 bytes
-rw-r--r--modules/ytdl/ytdl.html45
-rw-r--r--modules/ytdl/ytdl.js31
-rw-r--r--package.json21
-rwxr-xr-xwebserver.js120
23 files changed, 1311 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..adb9220
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+globalAccounts.json
+node_modules/
+persist/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b49c015
--- /dev/null
+++ b/index.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tom Smeding</title>
+<style>
+body{
+ font-family:"Courier New",Courier,Monospace;
+ text-align:center;
+}
+div.main-content{
+ display:inline-block;
+ max-width:800px;
+}
+/*.pulse-label{
+ transition:width 0.5s;
+}*/
+</style>
+</head>
+<body>
+<div class="main-content">
+ <h1>Hi!</h1>
+ <p>I'm Tom Smeding. I love programming, doing math and playing the piano. This is a simple website (on an <a href="https://www.digitalocean.com">advanced webserver</a>) that I kind of maintain for hosting various projects of mine.</p>
+ <p>You can reach me on <a href="https://www.facebook.com/tom.smeding">Facebook</a>, <a href="https://github.com/tomsmeding">Github</a> and <a href="https://telegram.me/tomsmeding">Telegram</a>, among others.</p>
+ <br>
+ <p>Since I started measuring, I pressed <span class="pulse-label"><!--<<PULSE-KEYS>>--></span> keys and clicked my mouse <span class="pulse-label"><!--<<PULSE-CLICKS>>--></span> times. <small><a href="http://whatpulse.org/tomsmeding/">More info</a>.</small></p>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/modules/$common.js b/modules/$common.js
new file mode 100644
index 0000000..fa29c50
--- /dev/null
+++ b/modules/$common.js
@@ -0,0 +1,33 @@
+var path=require("path"),
+ basicAuth=require("basic-auth");
+
+var cwd=process.cwd();
+var globalAccounts=require(cwd+"/globalAccounts.json");
+
+module.exports={
+ "rootdir":path.dirname(cwd),
+ "serverdir":cwd,
+ "simpleHTMLescape":function simpleHTMLescape(str){
+ return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+ },
+ "authgen":function authgen(accounts){ //omit `accounts` to use the globalAccounts list
+ if(!accounts)accounts=globalAccounts;
+ return function (req,res,next){
+ function unauth(res){
+ res.set("WWW-Authenticate","Basic realm=Authorization required");
+ return res.sendStatus(401);
+ };
+ var user=basicAuth(req);
+ if(!user||!user.name||!user.pass){
+ return unauth(res);
+ }
+ var i;
+ for(i=0;i<accounts.length;i++){
+ if(accounts[i][0]==user.name&&accounts[i][1]==user.pass){
+ return next();
+ }
+ }
+ return unauth(res);
+ };
+ },
+};
diff --git a/modules/abbrgen/abbreviation_gen_Darwin b/modules/abbrgen/abbreviation_gen_Darwin
new file mode 100755
index 0000000..7fd9596
--- /dev/null
+++ b/modules/abbrgen/abbreviation_gen_Darwin
Binary files differ
diff --git a/modules/abbrgen/abbreviation_gen_Linux b/modules/abbrgen/abbreviation_gen_Linux
new file mode 100755
index 0000000..dd151e7
--- /dev/null
+++ b/modules/abbrgen/abbreviation_gen_Linux
Binary files differ
diff --git a/modules/abbrgen/abbrgen.js b/modules/abbrgen/abbrgen.js
new file mode 100644
index 0000000..64a29b1
--- /dev/null
+++ b/modules/abbrgen/abbrgen.js
@@ -0,0 +1,49 @@
+var cmn=require("../$common.js"),
+ process=require("child_process");
+
+var moddir,uname;
+uname=String(process.execSync("uname")).trim();
+
+//PERFORMS NO VALIDATION!
+function get_abbreviations(abbr,num,cb){
+ var fname=moddir+"/abbreviation_gen_"+uname;
+ process.execFile(fname,[abbr,num],{},function(err,stdout,stderr){
+ //if(err)throw err;
+ if(err){
+ console.log(err.toString());
+ console.log(err.stack);
+ cb([]);
+ }
+ cb(stdout.split("\n"));
+ });
+}
+
+module.exports=function(app,io,_moddir){
+ moddir=_moddir;
+ app.get("/abbrgen/:abbr",function(req,res){
+ var abbr=req.params.abbr;
+ res.set("Content-Type","text/plain");
+ if(!abbr.match(/^[a-z]+$/i)){
+ res.send("ERROR: Invalid input value.");
+ return;
+ }
+ get_abbreviations(abbr,"1",function(answers){
+ res.send(answers.join("\n"));
+ });
+ });
+ app.get("/abbrgen/:abbr/:num",function(req,res){
+ var abbr=req.params.abbr,num=req.params.num;
+ res.set("Content-Type","text/plain");
+ if(num>5000){
+ res.send("ERROR: Number of abbreviations too large.");
+ return;
+ }
+ if(!abbr.match(/^[a-z]+$/i)||isNaN(+num)){
+ res.send("ERROR: Invalid input values.");
+ return;
+ }
+ get_abbreviations(abbr,num,function(answers){
+ res.send(answers.join("\n"));
+ });
+ });
+};
diff --git a/modules/changes/changes.html b/modules/changes/changes.html
new file mode 100644
index 0000000..cda3da1
--- /dev/null
+++ b/modules/changes/changes.html
@@ -0,0 +1,257 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Website change monitor</title>
+<script>
+"use strict";
+
+var urls=[];
+
+function pad(s,n,c){
+ s=s+"";
+ if(!c)c="0";
+ var fill=c,filllen=n-s.length;
+ if(filllen<=0)return s;
+ while(fill.length<filllen)fill+=c;
+ return fill+s;
+}
+
+function dateformat(date){
+ return date.getFullYear()+"/"+pad(date.getMonth(),2)+"/"+pad(date.getDate(),2)+" "+
+ pad(date.getHours(),2)+":"+pad(date.getMinutes(),2)+":"+pad(date.getSeconds(),2);
+}
+
+function fetch(method,url,data/*?*/,cb){
+ if(!cb){
+ cb=data;
+ data=undefined;
+ if(!cb)throw new Error("No callback passed to fetch");
+ }
+ var xhr=new XMLHttpRequest();
+ xhr.onreadystatechange=function(ev){
+ if(xhr.readyState<4)return;
+ cb(xhr.status,xhr.responseText);
+ };
+ xhr.open(method,url);
+ xhr.send(data);
+}
+
+function focusData(data){
+ document.getElementById("timelinecontainer").classList.add("visible");
+ var header=document.getElementById("timelineheader");
+ var tbody=document.getElementById("timelinetbody");
+ if(!header.firstChild)header.appendChild(document.createTextNode(data.url));
+ else header.firstChild.nodeValue=data.url;
+
+ var l=tbody.children,i;
+ for(i=l.length-1;i>=0;i--)tbody.removeChild(l[i]);
+
+ var tr,td,s;
+ for(i=data.timeline.length-1;i>=0;i--){
+ tr=document.createElement("tr");
+ if(i<data.timeline.length-1&&data.timeline[i][1]==data.timeline[i+1][1]){
+ tr.classList.add("repeated");
+ }
+
+ td=document.createElement("td");
+ td.appendChild(document.createTextNode(dateformat(new Date(data.timeline[i][0]))));
+ tr.appendChild(td);
+
+ td=document.createElement("td");
+ s=data.timeline[i][1];
+ if(s==null)s="(error while retrieving page)";
+ td.appendChild(document.createTextNode(s));
+ tr.appendChild(td);
+
+ tbody.appendChild(tr);
+ }
+}
+
+function makeURLtr(url){
+ var tr=document.createElement("tr");
+ var td=document.createElement("td");
+
+ td.appendChild(document.createTextNode(url));
+
+ var e=document.createElement("input");
+ e.type="button";
+ e.classList.add("rowdeletebutton");
+ e.value="X";
+ e.setAttribute("title","Delete URL");
+ e.addEventListener("click",function(ev){
+ ev.stopPropagation();
+ fetch("DELETE","/changes/url",url,function(status,body){
+ if(document.getElementById("timelineheader").firstChild.nodeValue==url){
+ document.getElementById("timelinecontainer").classList.remove("visible");
+ }
+ if(status!=200){
+ alert("Error deleting url: ("+status+") "+body);
+ return;
+ }
+ tr.parentNode.removeChild(tr);
+ updateURLs();
+ });
+ });
+ td.appendChild(e);
+
+ tr.addEventListener("click",function(ev){
+ fetch("GET","/changes/url?url="+encodeURIComponent(url),function(status,body){
+ if(status!=200){
+ alert("Error getting data: ("+status+") "+body);
+ return;
+ }
+ var data;
+ try {
+ data=JSON.parse(body);
+ } catch(e){
+ alert("Server sent invalid data!");
+ console.log("Invalid data:",data);
+ return;
+ }
+ focusData(data);
+ });
+ });
+
+ tr.appendChild(td);
+ return tr;
+}
+
+function updateURLs(){
+ fetch("GET","/changes/urls",function(status,body){
+ urls=JSON.parse(body);
+ if(!urls){
+ urls=[];
+ alert("Error retrieving URLs!");
+ return;
+ }
+ var tbody=document.getElementById("urlstable").firstElementChild;
+ var l=tbody.children,i,tr,td;
+ for(i=l.length-1;i>=0;i--)tbody.removeChild(l[i]);
+ for(i=0;i<urls.length;i++){
+ tbody.appendChild(makeURLtr(urls[i]));
+ }
+ });
+}
+
+function addURLbutton(){
+ var e=document.getElementById("urladdinput");
+ var url=e.value;
+ fetch("POST","/changes/url",url,function(status,body){
+ if(status==200){
+ e.value="";
+ } else {
+ alert("URL submission error: ("+status+") "+body);
+ }
+ updateURLs();
+ });
+}
+
+function addURLkeypress(ev){
+ if(ev.keyCode==10||ev.keyCode==13)addURLbutton();
+}
+
+function sendRefresh(){
+ fetch("POST","/changes/refresh",function(status,body){});
+}
+
+window.addEventListener("load",function(){
+ updateURLs();
+ //setInterval(updateURLs,10*1000);
+});
+</script>
+<style>
+body{
+ font-family:Georgia,Times,serif;
+ font-size:14px;
+}
+h1{
+ /*font-size:28px;*/
+ /*margin:19px 0 19px 0;*/
+}
+#urlscontainer{
+ width:500px;
+}
+#urlstablecontainer{
+ height:130px;
+ border:1px #888 solid;
+ overflow-y:scroll;
+}
+#urlstable{
+ width:100%;
+ border-collapse:collapse;
+}
+#urlstable tbody, #urlstable tr, #urlstable td{
+ width:100%;
+}
+#urlstable tr{
+ cursor:pointer;
+}
+#urlstable tr:hover{
+ background-color:#ddd;
+}
+#urlstable tr:nth-child(2n){
+ background-color:#eee;
+}
+#urlstable td{
+ height:100%;
+ vertical-align:middle;
+}
+#urladdinput{
+ width:300px;
+}
+.rowdeletebutton{
+ color:red;
+ font-family:Monospace;
+ font-weight:bold;
+ float:right;
+ cursor:pointer;
+ padding:0 3px 0 3px;
+ background-color:rgba(255,0,0,0.1);
+ border-radius:2px;
+ border-width:0;
+}
+
+#timelinetbl{
+ border-collapse:collapse;
+}
+#timelinetbl td, #timelinetbl th{
+ border:1px #888 solid;
+}
+
+tr.repeated{
+ color:#aaa;
+ font-size:10px;
+}
+
+#timelinecontainer{
+ display:none;
+}
+#timelinecontainer.visible{
+ display:block;
+}
+</style>
+</head>
+<body>
+<h1>Website change monitor</h1>
+<b>URLs:</b>
+<input type="button" onclick="sendRefresh();" value="Redownload sites">
+<br>
+<div>
+ <div id="urlscontainer">
+ <div id="urlstablecontainer">
+ <table id="urlstable"><tbody></tbody></table>
+ </div>
+ <input type="text" id="urladdinput" onkeypress="addURLkeypress(event);">
+ <input type="button" onclick="addURLbutton();" value="Add URL" title="Add URL to watch list">
+ </div>
+</div>
+<div id="timelinecontainer">
+ <h2 id="timelineheader"></h2>
+ <table id="timelinetbl">
+ <thead><tr><th>Date</th><th>Hash</th></tr></thead>
+ <tbody id="timelinetbody"></tbody>
+ </table>
+</div>
+</body>
+</html>
diff --git a/modules/changes/changes.js b/modules/changes/changes.js
new file mode 100644
index 0000000..6a8c8f2
--- /dev/null
+++ b/modules/changes/changes.js
@@ -0,0 +1,163 @@
+"use strict";
+
+var cmn=require("../$common.js"),
+ persist=require("node-persist"),
+ crypto=require("crypto"),
+ http=require("http"),
+ https=require("https"),
+ URL=require("url");
+
+var moddir=null;
+
+persist=persist.create({
+ dir:"persist/changes",
+ continuous:false,
+ interval:false
+});
+persist.initSync();
+
+//urls: map(url => URLobject)
+//url: String
+//URLobject: {url, timeline: [[Date, hash]]}
+var urls=persist.getItemSync("urls");
+if(!urls){
+ urls={};
+ persist.setItemSync("urls",urls);
+}
+
+
+function URLobject(url){
+ if(!(this instanceof URLobject))return new URLobject(url);
+ this.url=url;
+ this.timeline=[];
+}
+
+function fetch(method,url,data/*?*/,cb){
+ var cbcalled=false;
+
+ if(!cb){
+ cb=data;
+ data=undefined;
+ if(!cb)throw new Error("No callback passed to fetch");
+ }
+
+ try {
+ url=URL.parse(url);
+ } catch(e){
+ cb(-1,null);
+ return;
+ }
+
+ var httpclass;
+ switch(url.protocol){
+ case "http:": httpclass=http; break;
+ case "https:": httpclass=https; break;
+ default:
+ cb(-1,null);
+ return;
+ }
+
+ url.method=method;
+
+ var req=httpclass.request(url,function(res){
+ var body="";
+ res.on("data",function(data){
+ body+=data;
+ });
+ res.on("end",function(){
+ if(!cbcalled)cb(res.statusCode,body);
+ cbcalled=true;
+ });
+ res.on("error",function(err){
+ if(!cbcalled)cb(-1,err);
+ cbcalled=true;
+ });
+ });
+ req.on("error",function(err){
+ if(!cbcalled)cb(-1,err);
+ cbcalled=true;
+ });
+ if(data)req.write(data);
+ req.end();
+}
+
+function refreshURLs(){
+ var hashes={};
+ var i;
+ var url;
+ var timeout=null;
+ for(url in urls){
+ console.log("Fetching <"+url+">");
+ fetch("GET",url,function(url,status,body){
+ console.log("Got <"+url+">; status = "+status);
+ if(status==-1){
+ hashes[url]=[new Date(),null];
+ } else {
+ var hash=crypto.createHash("sha256");
+ hash.update(body);
+ hashes[url]=[new Date(),hash.digest("hex")];
+ }
+
+ if(!urls[url]){
+ console.log("WARNING: url <"+url+"> from hashes not found in urls!");
+ return;
+ }
+ //var last=urls[url].timeline[urls[url].timeline.length-1];
+ //if(last==undefined||hashes[url][1]==null||hashes[url][1]!=last[1]){
+ urls[url].timeline.push(hashes[url]);
+ //}
+
+ if(timeout)clearTimeout(timeout);
+ timeout=setTimeout(function(){
+ persist.setItemSync("urls",urls);
+ console.log("(persisted after refresh)")
+ },2000);
+ }.bind(null,url));
+ }
+}
+
+
+module.exports=function(app,io,_moddir){
+ moddir=_moddir;
+ app.all(["/changes","/changes/*"],cmn.authgen());
+ app.get("/changes",function(req,res){
+ res.sendFile(moddir+"/changes.html");
+ });
+ app.get("/changes/urls",function(req,res){
+ var list=[];
+ var url;
+ for(url in urls)list.push(url);
+ res.send(JSON.stringify(list));
+ });
+ app.get("/changes/url",function(req,res){
+ var url=req.query.url;
+ if(!urls[url]){
+ res.status(404);
+ res.send("URL not found in watch list");
+ return;
+ }
+ res.send(JSON.stringify(urls[url]));
+ });
+ app.post("/changes/url",function(req,res){
+ var url=req.body;
+ urls[url]=new URLobject(url);
+ persist.setItemSync("urls",urls);
+ // refreshURLs();
+ res.send();
+ });
+ app.delete("/changes/url",function(req,res){
+ var url=req.body;
+ if(!urls[url]){
+ res.status(404);
+ res.send("URL not found in watch list");
+ return;
+ }
+ delete urls[url];
+ persist.setItemSync("urls",urls);
+ res.send();
+ });
+ app.post("/changes/refresh",function(req,res){
+ refreshURLs();
+ res.send();
+ });
+};
diff --git a/modules/datumpjeprik/datumpjeprik.html b/modules/datumpjeprik/datumpjeprik.html
new file mode 100644
index 0000000..46a21d3
--- /dev/null
+++ b/modules/datumpjeprik/datumpjeprik.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Datumpje-prik</title>
+<script>
+var today,periodStart,periodEnd,periodWeekStart,periodWeekEnd,periodLength,
+ days,clicked,cantClick=false;
+
+//Sorry. Prototypes are fun. Using dollars to at least separate these from the built-in methods.
+Date.prototype.$dayOfWeek=function(){return (this.getDay()+6)%7;} //monday..sunday = 0..6
+Date.prototype.$startOfDay=function(){return new Date(this-(((this.getHours()*60+this.getMinutes())*60+this.getSeconds())*1000+this.getMilliseconds()));}
+Date.prototype.$addDays=function(n){return new Date(this.valueOf()+n*24*3600*1000);}
+Date.prototype.$addWeeks=function(n){return new Date(this.valueOf()+n*7*24*3600*1000);}
+Date.prototype.$naturalDate=function(){return this.getFullYear()+"-"+("00"+(this.getMonth()+1)).slice(-2)+"-"+("00"+this.getDate()).slice(-2);}
+
+function diffDays(d1,d2){return ~~(Math.abs(d1-d2)/1000/3600/24);}
+
+function localDateStringToDate(s){return new Date(s+"T00:00:00");}
+
+function appendPath(s,p){
+ if(s.length==0)return p;
+ if(p.length==0)return s;
+ if(s[s.length-1]=="/"){
+ if(p[0]=="/")return s+p.slice(1);
+ else return s+p;
+ } else {
+ if(p[0]=="/")return s+p;
+ else return s+"/"+p;
+ }
+}
+
+function setupCalendar(){
+ var cal=document.getElementById("calendar"),
+ maxnum=Math.max.apply(null,days)+1,
+ startOffset=diffDays(periodWeekStart,periodStart),
+ tr,td,span,w,d,idx;
+ cal.innerHTML="";
+ tr=document.createElement("tr");
+ for(d=0;d<7;d++){
+ td=document.createElement("td");
+ td.innerHTML=["mon","tue","wed","thu","fri","sat","sun"][d];
+ tr.appendChild(td);
+ }
+ cal.appendChild(tr);
+ for(w=0;w<~~(periodLength/7);w++){
+ tr=document.createElement("tr");
+ for(d=0;d<7;d++){
+ td=document.createElement("td");
+ if(7*w+d>=startOffset){
+ idx=7*w+d-startOffset;
+ span=document.createElement("span");
+ span.classList.add("calendar-day-fillspan");
+ span.setAttribute("style","width:"+(days[idx]+clicked[idx])/maxnum*100+"%");
+ td.appendChild(span);
+ span=document.createElement("span");
+ span.classList.add("calendar-day-datelabel");
+ span.appendChild(document.createTextNode(periodStart.$addDays(idx).$naturalDate()));
+ td.appendChild(span);
+ if(!cantClick&&!localStorage.getItem("hasSubmitted"))
+ (function(idx){
+ td.addEventListener("click",function(){
+ clicked[idx]=!clicked[idx];
+ setTimeout(setupCalendar,0);
+ },false);
+ })(idx);
+ }
+ tr.appendChild(td);
+ }
+ cal.appendChild(tr);
+ }
+ if(localStorage.getItem("hasSubmitted")){
+ document.getElementById("status").innerHTML="You have already submitted!";
+ document.getElementById("submitbtn").setAttribute("disabled","");
+ } else {
+ document.getElementById("status").style.display="none";
+ document.getElementById("submitbtn").removeAttribute("disabled");
+ }
+}
+
+function submitdays(){
+ var xhr;
+ cantClick=true;
+ xhr=new XMLHttpRequest();
+ xhr.addEventListener("readystatechange",function(ev){
+ var result;
+ if(this.readyState!=4)return;
+ alert(this.responseText);
+ result=JSON.parse(this.responseText);
+ if(result==true){
+ alert("Submitted successfully!");
+ localStorage.setItem("hasSubmitted","true")
+ } else alert("Error while submitting!");
+ },false);
+ xhr.open("POST",location.protocol+"//"+location.host+appendPath(location.pathname,"/adddays"));
+ xhr.send(JSON.stringify(clicked));
+}
+
+
+(function(){
+ var xhr;
+ today=new Date().$startOfDay();
+ xhr=new XMLHttpRequest();
+ xhr.addEventListener("readystatechange",function(ev){
+ var result;
+ if(this.readyState!=4)return;
+ result=JSON.parse(this.responseText);
+ periodStart=new Date(localDateStringToDate(result["periodStart"]));
+ periodEnd=new Date(localDateStringToDate(result["periodEnd"]));
+ periodWeekStart=periodStart.$addDays(-periodStart.$dayOfWeek());
+ periodWeekEnd=periodEnd.$addDays(6-periodEnd.$dayOfWeek());
+ periodLength=diffDays(periodStart,periodEnd)+1;
+ days=Array.apply(null,new Array(periodLength)).map(function(){return 0;});
+ clicked=days.map(function(){return false;});
+ setupCalendar();
+ },false);
+ xhr.open("GET",location.protocol+"//"+location.host+appendPath(location.pathname,"/dates"));
+ xhr.send();
+
+ xhr=new XMLHttpRequest();
+ xhr.addEventListener("readystatechange",function(ev){
+ var result;
+ if(this.readyState!=4)return;
+ days=JSON.parse(this.responseText);
+ setupCalendar();
+ },false);
+ xhr.open("GET",location.protocol+"//"+location.host+appendPath(location.pathname,"/days"));
+ xhr.send();
+})();
+
+window.addEventListener("load",function(){
+ ;
+},false);
+</script>
+<style>
+tbody#calendar > tr{
+ height:40px;
+}
+tbody#calendar > tr > td{
+ position:relative;
+ width:80px;
+ height:inherit;
+ text-align:center;
+ vertical-align:middle;
+ border:1px #ddd solid;
+}
+.calendar-day-fillspan{
+ display:inline-block;
+ position:absolute;
+ background-color:#6f6;
+ opacity:40%;
+ left:0;
+ top:0;
+ height:100%;
+ z-index:-10000;
+}
+.calendar-day-datelabel{
+ color:#888;
+ font-size:8pt;
+}
+</style>
+</head>
+<body>
+<div id="status"></div>
+<table><tbody id="calendar"><tr><td>Loading...</td></tr></tbody></table>
+<input id="submitbtn" type="button" onclick="submitdays()" value="Submit your days">
+</body>
+</html>
diff --git a/modules/datumpjeprik/datumpjeprik.js b/modules/datumpjeprik/datumpjeprik.js
new file mode 100644
index 0000000..dc56759
--- /dev/null
+++ b/modules/datumpjeprik/datumpjeprik.js
@@ -0,0 +1,66 @@
+var cmn=require("../$common.js");
+var persist=require("node-persist"),
+ util=require("util");
+
+//Sorry. Prototypes are fun. Using dollars to at least separate these from the built-in methods.
+Date.prototype.$dayOfWeek=function(){return (this.getDay()+6)%7;} //monday..sunday = 0..6
+Date.prototype.$startOfDay=function(){return new Date(this-(((this.getHours()*60+this.getMinutes())*60+this.getSeconds())*1000+this.getMilliseconds()));}
+Date.prototype.$addDays=function(n){return new Date(this.valueOf()+n*24*3600*1000);}
+Date.prototype.$addWeeks=function(n){return new Date(this.valueOf()+n*7*24*3600*1000);}
+Date.prototype.$naturalDate=function(){return this.getFullYear()+"-"+("00"+(this.getMonth()+1)).slice(-2)+"-"+("00"+this.getDate()).slice(-2);}
+
+function diffDays(d1,d2){return ~~(Math.abs(d1-d2)/1000/3600/24);}
+
+function localDateStringToDate(s){return new Date(s+"T00:00:00");}
+
+
+var periodStart="2015-04-09",periodEnd="2015-07-05",
+ periodLength=diffDays(localDateStringToDate(periodStart),localDateStringToDate(periodEnd))+1;
+
+persist=persist.create({
+ dir:"persist/datumpjeprik",
+ continuous:false,
+ interval:false
+});
+persist.initSync();
+
+function ensureInitialised(){
+ var days;
+ days=persist.getItem("days");
+ if(!days||days.length!=periodLength){
+ days=Array.apply(null,new Array(periodLength)).map(function(){return 0;});
+ persist.setItemSync("days",days);
+ }
+}
+ensureInitialised();
+
+module.exports=function(app,io,moddir){
+ app.get("/datumpjeprik",function(req,res){
+ res.set("Content-Type","text/html");
+ res.sendFile(moddir+"/datumpjeprik.html");
+ });
+ app.get("/datumpjeprik/dates",function(req,res){
+ res.set("Content-Type","text/json");
+ res.send('{"periodStart":"'+periodStart+'","periodEnd":"'+periodEnd+'"}');
+ });
+ app.get("/datumpjeprik/days",function(req,res){
+ ensureInitialised();
+ res.set("Content-Type","text/json");
+ res.send(persist.getItem("days"));
+ });
+ app.post("/datumpjeprik/adddays",function(req,res){
+ var newdays;
+ console.log(req.body);
+ try{
+ newdays=JSON.parse(req.body);
+ if(newdays.length!=periodLength)throw new Error("length mismatch");
+ if(!newdays.map(function(v){return typeof v=="boolean";}).reduce(function(a,b){return a&&b;}))throw new Error("not all bools");
+ ensureInitialised();
+ persist.setItemSync("days",persist.getItem("days").map(function(v,i){return v+newdays[i];}));
+ res.send("true");
+ } catch(e){
+ res.send("[false,\""+e.message+"\"]");
+ return;
+ }
+ });
+};
diff --git a/modules/ip/ip.js b/modules/ip/ip.js
new file mode 100644
index 0000000..dec0a98
--- /dev/null
+++ b/modules/ip/ip.js
@@ -0,0 +1,8 @@
+var cmn=require("../$common.js"),
+ fs=require("fs");
+
+module.exports=function(app,io,moddir){
+ app.get("/ip",function(req,res){
+ res.send(String(req.socket.remoteAddress));
+ });
+};
diff --git a/modules/nlen/English-Dutch-mistranslations.json b/modules/nlen/English-Dutch-mistranslations.json
new file mode 100644
index 0000000..dff7229
--- /dev/null
+++ b/modules/nlen/English-Dutch-mistranslations.json
@@ -0,0 +1,31 @@
+[
+{"nl":"lijge verspreidingen","en":"linux distributions"},
+{"nl":"draadjes","en":"threads"},
+{"nl":"lees-uitvoer-schrijf-cirkel","en":"REPL"},
+{"nl":"ventilator","en":"fan"},
+{"nl":"raamwerk","en":"framework"},
+{"nl":"reagerend","en":"reactive"},
+{"nl":"schoenlipje","en":"bootstrap"},
+{"nl":"materiaal ontwerp","en":"material design"},
+{"nl":"trappende stijl pagina's","en":"CSS"},
+{"nl":"C scherp","en":"C#"},
+{"nl":"Ga","en":"Go"},
+{"nl":"voorkanteinde","en":"front-end"},
+{"nl":"volledige stapel","en":"full-stack"},
+{"nl":"Meteoor, een raamwerk voor reagerende alleen pagina webtoepassingen","en":"Meteor, a framework for single-page web apps"},
+{"nl":"stapeloverloop","en":"stackoverflow"},
+{"nl":"rentijd","en":"runtime"},
+{"nl":"werkelijktijd","en":"realtime"},
+{"nl":"zachte goederen","en":"software"},
+{"nl":"harde goederen","en":"hardware"},
+{"nl":"Hyper-Tekst Markeer-Op Taal","en":"HTML"},
+{"nl":"client","en":"cliënt"},
+{"nl":"serveerder","en":"server"},
+{"nl":"databasis","en":"database"},
+{"nl":"voorhalen","en":"pre-fetch"},
+{"nl":"methoderoepingen","en":"method calls"},
+{"nl":"blauwdruk","en":"template"},
+{"nl":"werktuigen","en":"tools"},
+{"nl":"Toepassing-Programmeer-Tussengezicht","en":"API"},
+{"nl":"Principes van Meteoor\nData op het Snoer. Meteor stuurt geen HTMOT (Hyper Tekst Markeer-Op Taal) over het netwerk. De server stuurt gegevens en laat de cliënt deze weergeven.\nÉén Taal. Meteoor laat je beide de cliënt- en de serveerderdelen van je toepassing in JavaScript schrijven.\nDatabasis Overal. Je kan dezelfde methodes gebruiken om je databasis te bereiken van de cliënt of de serveerder.\nWachttijdcompensatie. Op de cliënt, Meteoor voorhaalt gegevens op en simuleert modellen om het te maken lijken alsof de serveerder methode roepingen instantaan terugkomen.\nVolle-stapel reagerendheid. In Meteoor, werkelijktijd is de standaard. Alle lagen, van databasis tot blauwdruk, vernieuwen zichzelf automatisch wanneer benodigd.\nOmarm het ecosysteem. Meteoor is open bron en integreert met existerende open bron werktuigen en raamwerken.\nEenvoudigheid Evenaart Productiviteit. De beste weg om iets simpel te laten lijken is om het werkelijk simpel te hebben zijn. Meteoor's hoofdfunctionaliteit heeft schone, klassiek-mooie TPT's (Toepassing Programmeer Tussengezichten).","en":"Principles of Meteor\nData on the Wire. Meteor doesn't send HTML over the network. The server sends data and lets the client render it.\nOne Language. Meteor lets you write both the client and the server parts of your application in JavaScript.\nDatabase Everywhere. You can use the same methods to access your database from the client or the server.\nLatency Compensation. On the client, Meteor prefetches data and simulates models to make it look like server method calls return instantly.\nFull Stack Reactivity. In Meteor, realtime is the default. All layers, from database to template, update themselves automatically when necessary.\nEmbrace the Ecosystem. Meteor is open source and integrates with existing open source tools and frameworks.\nSimplicity Equals Productivity. The best way to make something seem simple is to have it actually be simple. Meteor's main functionality has clean, classically beautiful APIs."}
+] \ No newline at end of file
diff --git a/modules/nlen/nlen.html b/modules/nlen/nlen.html
new file mode 100644
index 0000000..dd09e58
--- /dev/null
+++ b/modules/nlen/nlen.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>NL/EN mistranslations</title>
+</head>
+<body>
+<!--DUTCHDUTCH--><br>
+<!--ENGLISHENGLISH--><br>
+</body>
+</html>
diff --git a/modules/nlen/nlen.js b/modules/nlen/nlen.js
new file mode 100644
index 0000000..c988105
--- /dev/null
+++ b/modules/nlen/nlen.js
@@ -0,0 +1,17 @@
+var cmn=require("../$common.js"),
+ fs=require("fs");
+
+var translations;
+
+module.exports=function(app,io,moddir){
+ translations=eval(String(fs.readFileSync(moddir+"/English-Dutch-mistranslations.json")));
+ app.get("/nlen",function(req,res){
+ var translation=translations[+new Date()/60000%translations.length|0]; //new translation each minute
+ res.set('Content-Type', 'text/html');
+ res.send(
+ String(fs.readFileSync(moddir+"/nlen.html"))
+ .replace("<!--DUTCHDUTCH-->",cmn.simpleHTMLescape(translation.nl))
+ .replace("<!--ENGLISHENGLISH-->",cmn.simpleHTMLescape(translation.en))
+ );
+ });
+};
diff --git a/modules/poke/poke.html b/modules/poke/poke.html
new file mode 100644
index 0000000..de9ce1f
--- /dev/null
+++ b/modules/poke/poke.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Poke</title>
+<script src="/socket.io/socket.io.js"></script>
+<script>
+var socket=io("/poke");
+var conns=[];
+var self_info={id:-1,nick:"",status:""};
+
+function pokeLink(cOrId){
+ var c;
+ if(typeof cOrId=="number"){
+ for(var i=0;i<conns.length;i++)if(conns[i].id==cOrId)break;
+ if(i==conns.length)return document.createElement("span");
+ c=conns[i];
+ } else c=cOrId;
+ var span=document.createElement("span");
+ if(c.id==self_info.id)span.classList.add("pokelink_self");
+ else span.classList.add("pokelink");
+ span.appendChild(document.createTextNode(c.nick));
+ if(c.id!=self_info.id){
+ span.addEventListener("click",function(ev){socket.emit("poke",c.id);});
+ }
+ return span;
+}
+
+socket.on("reset",function(){
+ conns=[];
+ var ul=document.getElementById("connectionlist"),l=ul.children;
+ for(var i=l.length-1;i>=0;i--)ul.removeChild(l[i]);
+ ul=document.getElementById("notificationlist");l=ul.children;
+ for(var i=l.length-1;i>=0;i--)ul.removeChild(l[i]);
+});
+socket.on("self",function(s_i){
+ self_info=s_i;
+});
+socket.on("connection add",function(c){
+ conns.push(c);
+ var li=document.createElement("li");
+ li.classList.add("connection");
+ li.setAttribute("poke_id",c.id);
+ li.appendChild(pokeLink(c));
+ var span=document.createElement("span");
+ span.classList.add("connectionstatus");
+ span.appendChild(document.createTextNode(""));
+ li.appendChild(span);
+ document.getElementById("connectionlist").appendChild(li);
+});
+socket.on("connection remove",function(id){
+ var i;
+ for(i=0;i<conns.length;i++)if(conns[i].id==id)break;
+ if(i==conns.length)return; //?
+ conns.splice(i,1);
+ var ul=document.getElementById("connectionlist"),l=ul.children;
+ for(i=0;i<l.length;i++)if(l[i].getAttribute("poke_id")==id)break;
+ if(i==l.length)return; //?
+ ul.removeChild(l[i]);
+});
+socket.on("status",function(obj){
+ var id=obj.id,st=obj.status;
+ var i;
+ for(i=0;i<conns.length;i++)if(conns[i].id==id)break;
+ if(i==conns.length)return;
+ conns[i].status=st;
+ var ul=document.getElementById("connectionlist"),l=ul.children;
+ for(i=0;i<l.length;i++)if(l[i].getAttribute("poke_id")==id)break;
+ if(i==l.length)return; //?
+ l[i].children[1].firstChild.nodeValue=st?"("+st+")":"";
+});
+socket.on("poke",function(id){
+ var notiflist=document.getElementById("notificationlist");
+ var li=document.createElement("li");
+ li.classList.add("notification");
+ var span=document.createElement("span");
+ span.appendChild(document.createTextNode("Poke from "));
+ span.appendChild(pokeLink(id));
+ li.appendChild(span);
+ notiflist.appendChild(li);
+ setTimeout(function(){
+ notiflist.removeChild(li);
+ },3000);
+});
+
+function changeStatus(st){
+ socket.emit("status",st);
+}
+</script>
+<style>
+body{
+ font-family:Monaco,Monospace;
+}
+span.pokelink{
+ color:#44f;
+ cursor:pointer;
+ transition:color .3s;
+}
+span.pokelink:hover{
+ color:#77f;
+}
+ul#notificationlist{
+ list-style-type:none;
+}
+span.connectionstatus{
+ font-size:small;
+ margin-left:10px;
+ font-style:italic;
+}
+</style>
+</head>
+<body>
+<table style="width:100%"><tbody><tr>
+ <td style="vertical-align:top">
+ <h2>Users</h2>
+ <ul id="connectionlist"></ul>
+ </td>
+ <td style="vertical-align:top;text-align:right">
+ <h2>Notifications</h2>
+ <ul id="notificationlist"></ul>
+ </td>
+</tr></tbody></table>
+<input type="text" placeholder="change status..." size="30" style="margin-top:20px" onkeypress="if(event.keyCode!=13)return true;changeStatus(this.value);this.value=''">
+</body>
+</html> \ No newline at end of file
diff --git a/modules/poke/poke.js b/modules/poke/poke.js
new file mode 100644
index 0000000..af0285d
--- /dev/null
+++ b/modules/poke/poke.js
@@ -0,0 +1,47 @@
+var cmn=require("../$common.js"),
+ fs=require("fs"),
+ Naampje=require("naampje").name;
+
+var conns=[];
+
+var uniqid=(function(){
+ var id=0;
+ return function(){return id++;};
+})();
+
+module.exports=function(app,io,moddir){
+ var ioNsp=io.of("/poke");
+ app.get("/poke",function(req,res){
+ res.sendFile(moddir+"/poke.html");
+ });
+ ioNsp.on("connection",function(socket){
+ var id=uniqid();
+ var nick=Naampje();
+ var status="";
+ socket.emit("reset",null);
+ socket.emit("self",{id:id,nick:nick});
+ conns.forEach(function(c){
+ socket.emit("connection add",{id:c.id,nick:c.nick});
+ socket.emit("status",{id:c.id,status:c.status});
+ });
+ ioNsp.emit("connection add",{id:id,nick:nick});
+ conns.push({id:id,nick:nick,status:"",socket:socket});
+ socket.on("disconnect",function(){
+ for(var i=0;i<conns.length;i++)if(conns[i].id==id)break;
+ if(i!=conns.length)conns.splice(i,1);
+ ioNsp.emit("connection remove",id);
+ });
+ socket.on("status",function(newst){
+ status=newst.toString();
+ for(var i=0;i<conns.length;i++)if(conns[i].id==id)break;
+ if(i==conns.length)return; //?
+ conns[i].status=status;
+ ioNsp.emit("status",{id:id,status:status});
+ });
+ socket.on("poke",function(target){
+ for(var i=0;i<conns.length;i++)if(conns[i].id==target)break;
+ if(i==conns.length)return; //hah.
+ conns[i].socket.emit("poke",id);
+ });
+ });
+};
diff --git a/modules/quotesgrab/quotesgrab.js b/modules/quotesgrab/quotesgrab.js
new file mode 100644
index 0000000..6b3b068
--- /dev/null
+++ b/modules/quotesgrab/quotesgrab.js
@@ -0,0 +1,65 @@
+var cmn=require("../$common.js"),
+ https=require("https");
+
+function parsequoteshtml(html){
+ var idx=html.indexOf("row-header-wrapper"); if(idx==-1)return []; idx+=18;
+ var endidx=html.indexOf("<tbody>",idx);
+ var idx2;
+ var list=[],obj;
+ var keylist=["timestamp","name","quote"],i;
+ while(true){
+ idx=html.indexOf("row-header-wrapper",idx); if(idx==-1)break; idx+=18;
+ if(idx>endidx)break;
+ obj={};
+ for(i=0;i<keylist.length;i++){
+ idx=html.indexOf("<td",idx); if(idx==-1)break; idx+=3;
+ idx=html.indexOf(">",idx); if(idx==-1)break; idx+=1;
+ idx2=html.indexOf("</td>",idx); if(idx2==-1)break;
+ obj[keylist[i]]=
+ html.slice(idx,idx2)
+ .replace(/<\/?div[^>]*>/g,"")
+ .replace(/<br>/g,"\n")
+ .replace(/&quot;/g,'"')
+ .replace(/&amp;/g,"&")
+ .replace(/&#x([0-9a-f][0-9a-f]);/gi,function(match,p1){
+ return String.fromCharCode(parseInt(p1,16));
+ })
+ .replace(/&#([0-9]{1,3});/g,function(match,p1){
+ return String.fromCharCode(parseInt(p1,10));
+ });
+ idx=idx2+5;
+ }
+ if(obj.name=="x"||obj.name=="X")continue;
+ obj.timestamp=new Date(obj.timestamp);
+ list.push(obj);
+ }
+ return list;
+}
+
+function getquoteslist(cb){
+ https.get("https://docs.google.com/spreadsheets/d/1ywrThdscubPOC-gHh_qnFGfuPrtYxTap6UsJBDnt88c/htmlview",function(res){
+ var body="";
+ res.on("data",function(data){
+ body+=data.toString();
+ });
+ res.on("end",function(){
+ cb(parsequoteshtml(body));
+ });
+ res.on("error",function(err){
+ console.log("Error in quotes res:",err);
+ cb("");
+ });
+ }).on("error",function(err){
+ console.log("Error in quotes https:",err);
+ cb("");
+ });
+}
+
+module.exports=function(app,io,moddir){
+ app.get("/quotes",function(req,res){
+ res.set("Content-Type", "text/json");
+ getquoteslist(function(obj){
+ res.end(JSON.stringify(obj));
+ });
+ });
+};
diff --git a/modules/subd-krokodil/subd-krokodil.js b/modules/subd-krokodil/subd-krokodil.js
new file mode 100644
index 0000000..beabddd
--- /dev/null
+++ b/modules/subd-krokodil/subd-krokodil.js
@@ -0,0 +1,21 @@
+var cmn=require("../$common.js"),
+ fs=require("fs"),
+ express=require("express");
+
+module.exports=function(app,io,moddir){
+ var router=express.Router();
+
+ router.use(function(req,res,next){
+ next();
+ });
+
+ router.get("/",function(req,res){
+ res.sendFile(cmn.rootdir+"/web_files/krokodil.jpg");
+ });
+
+ app.use(function(req,res,next){
+ if(req.subdomains.length&&req.subdomains[req.subdomains.length-1]=="krokodil"){
+ router.handle(req,res,next);
+ } else next();
+ });
+};
diff --git a/modules/ytdl/youtube-dl-Darwin b/modules/ytdl/youtube-dl-Darwin
new file mode 100755
index 0000000..97238a0
--- /dev/null
+++ b/modules/ytdl/youtube-dl-Darwin
Binary files differ
diff --git a/modules/ytdl/ytdl.html b/modules/ytdl/ytdl.html
new file mode 100644
index 0000000..e054b97
--- /dev/null
+++ b/modules/ytdl/ytdl.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>YTDL</title>
+<script>
+function retrieve_link(){
+ var link=document.getElementById("inputlink").value;
+ var xhr=new XMLHttpRequest();
+ xhr.addEventListener("readystatechange",function(){
+ if(xhr.readyState<4){
+ show_output(["(waiting to send)","(request opened)","(headers received)","(loading)"][xhr.readyState]);
+ }
+ if(xhr.readyState!=4)return;
+ var response;
+ if(xhr.responseText==null)response="(null)";
+ else response=xhr.responseText;
+ show_output(response,/^https?:\/\//.test(response));
+ });
+ xhr.open("GET","/ytdl/"+encodeURIComponent(link));
+ xhr.send();
+}
+
+function show_output(text,link){
+ var output=document.getElementById("output");
+ if(output.firstChild)output.removeChild(output.firstChild);
+ var e;
+ if(link){
+ e=document.createElement("a");
+ e.href=text;
+ e.appendChild(document.createTextNode(text));
+ } else e=document.createTextNode(text);
+ output.appendChild(e);
+}
+</script>
+</head>
+<body>
+<h1>YTDL</h1>
+<p>This page uses the <code>youtube-dl</code> utility to provide a download link for a given youtube video. It just selects whatever format <code>youtube-dl</code> considers "best"; this is normally video+audio, in the highest quality provided by YouTube.
+Enter a link or a video id below:</p>
+<input type="text" id="inputlink" size="100"><br>
+<input type="button" value="Retrieve link" onclick="retrieve_link()"><br>
+<pre id="output" style="width:100%;word-wrap:break-word;"></pre>
+</body>
+</html>
diff --git a/modules/ytdl/ytdl.js b/modules/ytdl/ytdl.js
new file mode 100644
index 0000000..3bbea16
--- /dev/null
+++ b/modules/ytdl/ytdl.js
@@ -0,0 +1,31 @@
+var cmn=require("../$common.js"),
+ child_process=require("child_process");
+
+module.exports=function(app,io,moddir){
+ app.param("ytdl_id",function(req,res,next,link){
+ link=unescape(link);
+ req.params.ytdl_id=link;
+ if(/['"\x00-\x1f]/.test(link)){
+ res.status(400).end("Invalid youtube link or id");
+ return;
+ }
+ next();
+ });
+ app.get("/ytdl",function(req,res){
+ res.sendFile(moddir+"/ytdl.html");
+ });
+ app.get("/ytdl/:ytdl_id",function(req,res){
+ var link=req.params.ytdl_id;
+ var cp=child_process.execFile("/usr/bin/env",["youtube-dl","-gf","best",link],{},function(err,stdout,stderr){
+ if(err){
+ //console.log(err);
+ res.status(404);
+ res.set("Content-Type","text/plain");
+ res.end("Youtube video id not found\n\n"+stdout+"\n\n"+stderr);
+ return;
+ }
+ res.set("Content-Type","text/plain");
+ res.send(stdout);
+ });
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0efce63
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "webserver",
+ "version": "1.0.0",
+ "description": "",
+ "main": "webserver.js",
+ "dependencies": {
+ "basic-auth": "^1.0.4",
+ "body-parser": "^1.15.2",
+ "express": "^4.14.0",
+ "express-subdomain": "^1.0.5",
+ "naampje": "^1.0.1",
+ "node-persist": "^2.0.2",
+ "socket.io": "^1.4.8"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Tom Smeding <tom.smeding@gmail.com> (http://tomsmeding.com)",
+ "license": "MIT"
+}
diff --git a/webserver.js b/webserver.js
new file mode 100755
index 0000000..e4b51b9
--- /dev/null
+++ b/webserver.js
@@ -0,0 +1,120 @@
+#!/usr/bin/env node
+var cmn=require("./modules/$common.js");
+var app=require("express")(),
+ http=require("http"),
+ httpServer=http.Server(app),
+ io=require("socket.io")(httpServer),
+ url=require("url"),
+ fs=require("fs"),
+ util=require("util"),
+ bodyParser=require("body-parser"),
+ basicAuth=require("basic-auth");
+
+
+if(process.argv.length>3){
+ console.log("Pass optional port as first argument");
+ process.exit(1);
+}
+var PORT=process.argv.length==3?+process.argv[2]:80;
+
+
+var whatpulse={"keys":"<??>","clicks":"<??>"};
+function refreshWhatpulse(){
+ http.get("http://api.whatpulse.org/user.php?user=tomsmeding&format=json&formatted=yes",function(res){
+ var body="";
+ res.on("data",function(data){body+=data;});
+ res.on("end",function(){
+ try{body=JSON.parse(body);}
+ catch(e){return;}
+ whatpulse.keys=body.Keys/*.replace(/,/g,"&nbsp;")*/;
+ whatpulse.clicks=body.Clicks/*.replace(/,/g,"&nbsp;")*/;
+ });
+ });
+}
+setInterval(refreshWhatpulse,6*3600*1000); //every 6 hours
+refreshWhatpulse();
+
+/*app.use(function (req, res, next) {
+ console.log(req.subdomains);
+ next();
+});*/
+
+app.use(bodyParser.text());
+
+
+var module_list=fs.readdirSync("modules").filter(function(f){
+ return ["$common.js",".DS_Store"].indexOf(f)==-1;
+});
+for(i=0;i<module_list.length;i++){
+ if(module_list[i]=="$common.js")continue;
+ require("./modules/"+module_list[i]+"/"+module_list[i]+".js")(app,io,cmn.serverdir+"/modules/"+module_list[i]);
+ console.log("Loaded module "+module_list[i]);
+}
+
+
+
+app.get("/",function(req,res){
+ res.send(
+ String(fs.readFileSync(cmn.serverdir+"/index.html"))
+ .replace(/<!--<<PULSE-KEYS>>-->/,whatpulse["keys"])
+ .replace(/<!--<<PULSE-CLICKS>>-->/,whatpulse["clicks"])
+ );
+});
+
+app.get("/f/univq/*",cmn.authgen());
+
+app.get("/f/*",function(req,res){
+ var parsed=url.parse(req.url),basefname=parsed.pathname.slice(2).replace(/\/\.+[^\/]*\//g,"/");
+ fname=cmn.rootdir+"/web_files"+basefname;
+ console.log("Requesting file "+fname);
+ var stats=fs.statSync(fname);
+ if(!fs.existsSync(fname))res.send("That file does not exist.");
+ else if(stats.isFile())res.sendFile(fname);
+ else if(stats.isDirectory()){
+ var items=fs.readdirSync(fname)
+ .filter(function(f){return f[0]!=".";})
+ .map(function(f){
+ if(fs.statSync(fname+"/"+f).isDirectory())return f+"/";
+ else return f;
+ });
+ res.send(
+ String(fs.readFileSync(cmn.rootdir+"/web_files/dirlisting.html"))
+ .replace("<!--DIRDIR-->",basefname)
+ .replace("[/*LISTINGLISTING*/]",JSON.stringify(items))
+ );
+ } else res.send("I don't recognise that file.");
+});
+
+["o","k","rip","rip2"].forEach(function(target){
+ app.get("/"+target,function(req,res){
+ res.sendFile(cmn.rootdir+"/web_files/"+target+".html");
+ });
+ app.get("/"+target+"/*",function(req,res){
+ res.set('Content-Type', 'text/html');
+ res.send(
+ String(fs.readFileSync(cmn.rootdir+"/web_files/"+target+".html"))
+ .replace("<!--MESSAGEMESSAGE-->",cmn.simpleHTMLescape(url.parse(req.url).pathname.slice(target.length+2)))
+ );
+ });
+});
+
+app.get("/dr",function(req,res){
+ res.sendFile(cmn.rootdir+"/web_files/duckroll.html");
+});
+
+app.get(["/gpg","/pgp","/gpg.asc","/pgp.asc"],function(req,res){
+ res.type("text/plain");
+ res.sendFile(cmn.rootdir+"/web_files/pgp.asc");
+});
+
+
+/*app.get("/chat",function(req,res){
+ res.redirect(301,"http://tomsmeding.com:81");
+});*/
+
+
+var server=httpServer.listen(PORT,function(){
+ var host=server.address().address;
+ var port=server.address().port;
+ console.log("Server listening at http://"+host+":"+port);
+});