summaryrefslogtreecommitdiff
path: root/client/client.js
diff options
context:
space:
mode:
Diffstat (limited to 'client/client.js')
-rwxr-xr-xclient/client.js363
1 files changed, 363 insertions, 0 deletions
diff --git a/client/client.js b/client/client.js
new file mode 100755
index 0000000..efd721c
--- /dev/null
+++ b/client/client.js
@@ -0,0 +1,363 @@
+#!/usr/bin/env node
+
+var fs=require("fs"),
+ path=require("path"),
+ https=require("https"),
+ crypto=require("crypto"),
+ read=require("read"),
+ toClipboard=require("to-clipboard"),
+ notifier=require("node-notifier");
+
+var HOSTNAME=process.argv[2]||"tomsmeding.com",HTTPSPORT=443;
+console.log("Using server "+HOSTNAME);
+
+
+var lockfiledesc;
+var lockfilename="/tmp/.lock_serverstore_client_"+HOSTNAME;
+try {lockfiledesc=fs.openSync(lockfilename,"wx");}
+catch(e){
+ var lockedpid=+fs.readFileSync(lockfilename);
+ var canoverwrite=false;
+ if(isNaN(lockedpid))canoverwrite=true;
+ try {process.kill(lockedpid,0);}
+ catch(e){canoverwrite=true;}
+ if(!canoverwrite){
+ console.log("Another copy of the serverstore client with the same hostname seems to be running.");
+ console.log("If this is not the case, or intentional, delete the file "+lockfilename+" and try again.");
+ process.exit(1);
+ }
+ lockfiledesc=fs.openSync(lockfilename,"w");
+}
+fs.writeSync(lockfiledesc,process.pid+"\n");
+process.on("exit",function(){
+ fs.closeSync(lockfiledesc);
+ fs.unlinkSync(lockfilename);
+});
+
+
+var userid="0",password="-";
+
+
+var HOMEDIR=process.env.HOME||process.env.HOMEPATH||process.env.HOMEDIR||process.cwd();
+var WATCHDIR;
+if(process.platform=="darwin"){
+ WATCHDIR=HOMEDIR+"/Desktop";
+} else {
+ WATCHDIR=HOMEDIR+"/screenshots";
+}
+var ignored=[];
+
+var currentState=[];
+
+function notify(message) {
+ notifier.notify({
+ title: "serverstore",
+ message: message,
+ });
+}
+
+//lots of code taken/modified from tomsmeding/gvajnez
+function collectDirState(dir){
+ if(!dir)dir=directory;
+ //console.log("collectDirState("+dir+")");
+ var list=fs.readdirSync(dir);
+ var result=[];
+ var statinfo,i,j;
+ for(i=0;i<list.length;i++){
+ for(j=0;j<ignored.length;j++){
+ if(etc.endsWith(path.normalize(dir+"/"+list[i]),"/"+ignored[j]))break;
+ }
+ if(j<ignored.length)continue;
+ statinfo=fs.statSync(dir+"/"+list[i]);
+ /*if(statinfo.isDirectory()){
+ result=result.concat(collectDirState(dir+"/"+list[i]));
+ } else if(statinfo.isFile()){*/
+ result.push({
+ name:path.resolve(dir+"/"+list[i]),
+ mode:statinfo.mode,
+ mtime:statinfo.mtime.getTime()
+ });
+ //}
+ }
+ return result;
+}
+
+function collectChanges(dir,state){
+ if(!dir)dir=directory;
+ var i,j,obj;
+ var changes=[];
+ for(i=0;i<state.length;i++){
+ for(j=0;j<currentState.length;j++){
+ if(state[i].name==currentState[j].name)break;
+ }
+ if(j==currentState.length||
+ currentState[j].mode!=state[i].mode||
+ currentState[j].mtime!=state[i].mtime){
+ //either file didn't exist yet, or metadata has changed
+ changes.push(state[i]);
+ }
+ }
+ for(i=0;i<currentState.length;i++){
+ for(j=0;j<state.length;j++){
+ if(currentState[i].name==state[j].name)break;
+ }
+ if(j==state.length){
+ //file doesn't exist anymore
+ changes.push(currentState[i]);
+ }
+ }
+ return changes;
+}
+
+function handleChanges(changes){
+ var i,match,namedate,now;
+ for(i=0;i<changes.length;i++){
+ if(!fs.existsSync(changes[i].name))continue; //probably delete event
+ match=changes[i].name.match(/\/Screen Shot (\d{4}-\d{2}-\d{2}) at (\d{2}\.\d{2}\.\d{2}).png$/);
+ if(!match)continue;
+ namedate=new Date(match[1]+" "+match[2].replace(/\./g,":"));
+ if(!namedate)continue;
+ changes[i].mtime=new Date(changes[i].mtime);
+ now=new Date();
+ if((now-changes[i].mtime)/1000>60||(now-namedate)/1000>60)continue; //1 minute limit
+ setTimeout(function(n){
+ return function(){sendfile(n);};
+ }(changes[i].name),100);
+ }
+}
+
+function authhash(challenge,password){
+ var s=challenge+password;
+ var hasher=crypto.createHash("sha256");
+ hasher.update(s);
+ return hasher.digest("hex");
+}
+
+function getchallenge(cb){
+ console.log("Going to request challenge...");
+ var req=https.request({
+ hostname:HOSTNAME,
+ port:HTTPSPORT,
+ path:"/ss/challenge",
+ method:"GET",
+ keepAlive:true //speed up the next request
+ },function(res){
+ var body="";
+ res.on("data",function(data){
+ body+=data;
+ });
+ res.on("end",function(){
+ if(res.statusCode!=200){
+ notify("Could not request challenge! Is your internet connection alive?\n"+body);
+ return;
+ }
+ console.log("challenge = "+body);
+ cb(body);
+ });
+ });
+ req.on("error",function(err){
+ console.log(err);
+ });
+ req.end();
+}
+function sendfile(fname,retries){
+ retries=retries!=null?retries:3;
+ var barefname;
+ var idx=fname.lastIndexOf("/");
+ if(idx==-1)barefname=fname;
+ else barefname=fname.slice(idx+1);
+ getchallenge(function(challenge){
+ var req=https.request({
+ hostname:HOSTNAME,
+ port:HTTPSPORT,
+ path:"/ss/image/"+userid+"/"+authhash(challenge,password)+"/"+escape(barefname),
+ method:"POST",
+ headers:{
+ "Content-Type":"image/png"
+ }
+ },function(res){
+ var body="";
+ res.on("data",function(data){
+ body+=data;
+ });
+ res.on("end",function(){
+ if(res.statusCode!=200){
+ if(retries>0)sendfile(fname,retries-1);
+ else notify("Could not upload image! Are your credentials still okay?\n"+body);
+ return;
+ }
+ console.log("Successful upload");
+ toClipboard.sync(body.trim());
+ notify("Uploaded, link copied");
+ try {
+ fs.unlinkSync(fname);
+ } catch(e){
+ console.log(e);
+ }
+ });
+ });
+ req.on("error",function(err){
+ console.log(err);
+ });
+ try{req.end(fs.readFileSync(fname));}
+ catch(e){console.log(e);}
+ });
+}
+
+
+function userExists(userid,cb){
+ var req=https.request({
+ hostname:HOSTNAME,
+ port:HTTPSPORT,
+ path:"/ss/exists/"+userid,
+ method:"GET"
+ },function(res){
+ if(res.statusCode==200)cb(true);
+ else if(res.statusCode==404)cb(false);
+ else {
+ console.log("Server returned status code "+res.statusCode+" for exists query!");
+ }
+ });
+ req.on("error",function(err){
+ console.log(err);
+ });
+ req.end();
+}
+
+function checkLogin(userid,password,cb,retries){
+ retries=retries!=null?retries:3;
+ getchallenge(function(challenge){
+ var req=https.request({
+ hostname:HOSTNAME,
+ port:HTTPSPORT,
+ path:"/ss/checklogin/"+userid+"/"+authhash(challenge,password),
+ method:"GET"
+ },function(res){
+ if(res.statusCode==200)cb(true);
+ else if(res.statusCode==404||res.statusCode==403){
+ if(retries>0)checkLogin(userid,password,cb,retries-1);
+ else cb(false);
+ } else {
+ console.log("Server returned status code "+res.statusCode+" for checklogin query!");
+ }
+ });
+ req.on("error",function(err){
+ console.log(err);
+ });
+ req.end();
+ });
+}
+
+function registerUser(userid,password){
+ var req=https.request({
+ hostname:HOSTNAME,
+ port:HTTPSPORT,
+ path:"/ss/registerx/"+userid,
+ method:"POST",
+ headers:{
+ "Content-Type":"text/plain"
+ }
+ },function(res){
+ var body="";
+ res.on("data",function(data){
+ body+=data;
+ });
+ res.on("end",function(){
+ if(res.statusCode==200)console.log("Successfully registered user "+userid);
+ else if(res.statusCode==409)console.log("Conflict: "+body);
+ else console.log("Error: "+body);
+ });
+ });
+ req.on("error",function(err){
+ console.log(err);
+ });
+ req.end(password);
+}
+
+var stdinFD = null;
+if (!process.stdin.isTTY) {
+ stdinFD = fs.openSync("/dev/stdin", "r");
+}
+
+function readStdinLine(options, callback) {
+ console.log("Entering read with " + JSON.stringify(options));
+ if (stdinFD) {
+ var input = "", buf = Buffer.alloc(1), ch;
+ while (true) {
+ if (fs.readSync(stdinFD, buf, 0, 1, null) < 1) {
+ console.log("Error reading from stdin");
+ process.exit(1);
+ }
+ ch = buf.toString()
+ if (ch == "\n") break;
+ input += ch;
+ }
+ callback(input);
+ } else {
+ read(options, function(err, res) {
+ console.log("Returned from read with " + JSON.stringify(options));
+ if (err) {
+ console.log("Error reading from stdin: " + err);
+ process.exit(1);
+ }
+ callback(res);
+ });
+ }
+}
+
+function readUserPass(callback) {
+ readStdinLine({prompt: "Username? "}, function(res) {
+ userid = res;
+
+ readStdinLine({prompt: "Password? ", silent: true}, function(passinput) {
+ var hasher = crypto.createHash("sha256");
+ hasher.update(passinput);
+ password = hasher.digest("hex");
+
+ callback();
+ });
+ });
+}
+
+
+
+readUserPass(function() {
+ console.log("\nChecking existence...");
+
+ userExists(userid,function(exists){
+ if(exists){
+ checkLogin(userid,password,function(ok){
+ if(ok)console.log("User login ok.");
+ else {
+ console.log("Username or password incorrect!");
+ process.exit();
+ }
+ });
+ return;
+ }
+ readStdinLine({prompt: "That username doesn't seem to exist. Register it? [y/N] "}, function(response) {
+ response = response[0];
+ if(response=="y"||response=="Y")registerUser(userid,password);
+ else {
+ console.log("Not registered. Exiting.");
+ process.exit();
+ }
+ });
+ });
+
+ var timeout=null;
+ var watcher=fs.watch(WATCHDIR,{persistent:true,recursive:false},function(ev,fname){
+ //console.log("change in directory "+WATCHDIR+" (fname "+fname+")");
+ if(timeout)return;
+ timeout=setTimeout(function(){
+ var newstate=collectDirState(WATCHDIR);
+ var changes=collectChanges(WATCHDIR,newstate).map(function(o){o.name=o.name.replace(/^\.\//,"");return o;});
+ currentState=newstate;
+ if(changes.length!=0)handleChanges(changes);
+ timeout=null;
+ //console.log(currentState);
+ },500);
+ });
+ currentState=collectDirState(WATCHDIR);
+ console.log("-- (Client ready.)");
+});