diff options
Diffstat (limited to 'client')
-rwxr-xr-x | client/client.js | 363 | ||||
-rw-r--r-- | client/package-lock.json | 71 | ||||
-rw-r--r-- | client/package.json | 28 | ||||
-rwxr-xr-x | client/startup.sh | 9 |
4 files changed, 471 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.)"); +}); diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..fb70d66 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,71 @@ +{ + "name": "serverstore-client", + "version": "1.0.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "requires": { + "mute-stream": "~0.0.4" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, + "to-clipboard": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/to-clipboard/-/to-clipboard-0.4.0.tgz", + "integrity": "sha1-pSpX+KTVZlVK8LKz++KUM6tH2oI=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..34084df --- /dev/null +++ b/client/package.json @@ -0,0 +1,28 @@ +{ + "name": "serverstore-client", + "version": "1.0.2", + "description": "A Puush alternative", + "main": "client.js", + "repository": { + "type": "git", + "url": "https://git.tomsmeding.com/serverstore" + }, + "dependencies": { + "node-notifier": "^5.4.0", + "read": "^1.0.7", + "to-clipboard": "~0" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "image", + "hosting", + "server", + "client", + "screenshot" + ], + "author": "Tom Smeding <tom.smeding@gmail.com> (https://tomsmeding.com)", + "license": "MIT" +} diff --git a/client/startup.sh b/client/startup.sh new file mode 100755 index 0000000..a9ee756 --- /dev/null +++ b/client/startup.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" + +echo "Enter username \n password \n" +IFS="" read username +IFS="" read -s password +printf "%s\n%s\n" "$username" "$password" | nohup ./client.js >client.out 2>&1 & +echo $! +disown |