summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rwxr-xr-xclient/client.js363
-rw-r--r--client/package-lock.json71
-rw-r--r--client/package.json28
-rwxr-xr-xclient/startup.sh9
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