const express = require("express"); const axios = require("axios"); const twitchStreams = require("twitch-get-stream"); const app = express(); const port = 4501; const { json, urlencoded } = require("body-parser"); const Pool = require("pg").Pool; app.use(json()); app.use( urlencoded({ extended: true, }) ); const nodemailer = require("nodemailer"); const fileUpload = require("express-fileupload"); const unzipper = require("unzipper"); const fs = require("fs"); const moment = require("moment"); const { exec, spawn } = require("child_process"); app.use( fileUpload({ createParentPath: true, limits: { fileSize: 15 * 1024 * 1024, files: 1 }, safeFileNames: true, preserveExtension: true, abortOnLimit: true, uploadTimeout: 0, }) ); const QuickChart = require("quickchart-js"); let allowCrossDomain = function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); res.header("Access-Control-Allow-Methods", "*"); next(); }; app.use(allowCrossDomain); const db = new Pool({ user: "postgres", password: "", host: "postgres", database: "divar", port: 5432, }); const db2 = new Pool({ user: "postgres", password: "", host: "postgres", database: "ngsplanner2", port: 5432, }); app.use((req, _, next) => { req.db = db; next() }) exports.app = app; require("./routes/d4dj-routes"); const ENDPOINTDATA = [ { endpoint: "class", requiredfields: ["name"], optionalfields: ["icon"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "class_level_data", requiredfields: ["class_id", "level", "hp", "atk", "def", "name"], optionalfields: [], excludedfields: [], //Fields to not output in GET. }, { endpoint: "class_weapon_type_data", requiredfields: ["class_id", "weapon_type_id"], optionalfields: [], excludedfields: [], //Fields to not output in GET. }, { endpoint: "weapon", requiredfields: ["name", "rarity", "level_req", "atk"], optionalfields: [ "potential_id", "variance", "base_affix_slots", "drop_info", "pb_gauge_build", "icon", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "weapon_existence_data", requiredfields: ["weapon_type_id", "weapon_id"], optionalfields: ["popularity", "editors_choice"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "weapon_type", requiredfields: ["name", "dmg_type"], optionalfields: ["icon"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "armor", requiredfields: ["name", "rarity", "level_req", "def"], optionalfields: [ "hp", "pp", "mel_dmg", "rng_dmg", "tec_dmg", "crit_rate", "crit_dmg", "pp_cost_reduction", "active_pp_recovery", "natural_pp_recovery", "dmg_res", "all_down_res", "burn_res", "freeze_res", "blind_res", "shock_res", "panic_res", "poison_res", "battle_power_value", "pb_gauge_build", "icon", "popularity", "editors_choice", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "potential", requiredfields: ["name"], optionalfields: ["icon"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "potential_data", requiredfields: ["potential_id", "level", "name"], optionalfields: [ "mel_dmg", "rng_dmg", "tec_dmg", "crit_rate", "crit_dmg", "pp_cost_reduction", "active_pp_recovery", "natural_pp_recovery", "dmg_res", "all_down_res", "burn_res", "freeze_res", "blind_res", "shock_res", "panic_res", "poison_res", "battle_power_value", "pb_gauge_build", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "builds", requiredfields: [ "users_id", "creator", "build_name", "class1", "created_on", "last_modified", "data", ], optionalfields: ["class2", "likes", "editors_choice"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "skill", requiredfields: ["name", "skill_type_id"], optionalfields: ["icon"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "skill_type", requiredfields: ["name"], optionalfields: [], excludedfields: [], //Fields to not output in GET. }, { endpoint: "skill_data", requiredfields: ["skill_id", "level", "name"], optionalfields: [ "variance", "mel_dmg", "rng_dmg", "tec_dmg", "crit_rate", "crit_dmg", "pp_cost_reduction", "active_pp_recovery", "natural_pp_recovery", "dmg_res", "popularity", "editors_choice", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "augment", requiredfields: ["augment_type_id", "name"], optionalfields: [ "variance", "hp", "pp", "mel_dmg", "rng_dmg", "tec_dmg", "crit_rate", "crit_dmg", "pp_cost_reduction", "active_pp_recovery", "natural_pp_recovery", "dmg_res", "affix_success_rate", "all_down_res", "burn_res", "freeze_res", "blind_res", "shock_res", "panic_res", "poison_res", "battle_power_value", "pb_gauge_build", "popularity", "editors_choice", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "augment_type", requiredfields: ["name"], optionalfields: ["icon"], excludedfields: [], //Fields to not output in GET. }, { endpoint: "food", requiredfields: ["name"], optionalfields: [ "potency", "pp", "dmg_res", "hp", "pp_consumption", "pp_recovery", "weak_point_dmg", "hp_recovery", "popularity", "editors_choice", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "food_mult", requiredfields: ["amount"], optionalfields: [ "potency", "pp", "dmg_res", "hp", "pp_consumption", "pp_recovery", "weak_point_dmg", "hp_recovery", ], excludedfields: [], //Fields to not output in GET. }, { endpoint: "roles", requiredfields: ["name"], optionalfields: [], excludedfields: [], //Fields to not output in GET. }, { endpoint: "users", requiredfields: ["username", "email", "created_on", "roles_id"], optionalfields: ["avatar", "editors_choice"], excludedfields: ["password_hash"], //Fields to not output in GET. }, { endpoint: "database_audit", requiredfields: [ "action", "table_name", "row_name", "row_id", "new_value", "date", "users_id", ], optionalfields: ["old_value"], excludedfields: [], //Fields to not output in GET. }, ]; function CreateDynamicEndpoints() { ENDPOINTDATA.map((endpoint) => { app.get("/ngsplanner/" + endpoint.endpoint, (req, res) => { if (endpoint.requiredfields.includes("name")) { db2 .query( "select distinct on (name) name,* from " + endpoint.endpoint + " order by name,id desc" ) .then((data) => { res.status(200).json({ fields: data.fields, rows: data.rows }); }) .catch((err) => { res.status(500).send(err.message); }); } else { db2 .query("select * from " + endpoint.endpoint + " order by id desc") .then((data) => { res.status(200).json({ fields: data.fields, rows: data.rows }); }) .catch((err) => { res.status(500).send(err.message); }); } }); app.post("/ngsplanner/" + endpoint.endpoint, (req, res) => { var allExist = true; endpoint.requiredfields.forEach((field) => { if (!(field in req.body)) { allExist = false; } }); if (!allExist) { res .status(300) .send( "Required fields are: " + endpoint.requiredfields .filter((field) => !(field in req.body)) .join(",") ); return; } var combinedfields = [ ...endpoint.requiredfields, ...endpoint.optionalfields, ...endpoint.excludedfields, ]; //console.log(combinedfields) var all_filled_fields = combinedfields.filter( (field) => field in req.body ); db2 .query( "insert into " + endpoint.endpoint + "(" + all_filled_fields.join(",") + ") values(" + all_filled_fields.map((field, i) => "$" + (i + 1)).join(",") + ") returning *", all_filled_fields.map((field) => req.body[field]) ) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); }); app.patch("/ngsplanner/" + endpoint.endpoint, (req, res) => { if (req.body.id) { var combinedfields = [ ...endpoint.requiredfields, ...endpoint.optionalfields, ...endpoint.excludedfields, ]; //console.log(combinedfields) var all_filled_fields = combinedfields.filter( (field) => field in req.body ); db2 .query( "update " + endpoint.endpoint + " set " + all_filled_fields .map((field, i) => field + "=$" + (i + 1)) .join(",") + " where id=$" + (all_filled_fields.length + 1) + " returning *", [...all_filled_fields.map((field) => req.body[field]), req.body.id] ) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(300).send("Invalid query!"); } }); app.delete("/ngsplanner/" + endpoint.endpoint, (req, res) => { if (req.body.id) { db2 .query( "delete from " + endpoint.endpoint + " where id=$1 returning *", [req.body.id] ) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(300).send("Invalid query!"); } }); }); } /*const db2 = new Pool({ user: 'read_only_user', password: 'divar1234', host: 'postgres', database: 'divar', port: 5432, }) app.get('/event/query',(req,res)=>{ if (req.query.query&&req.query.query.length>0) { db2.query(req.query.query) .then((data)=>{ res.status(200).json(data.rows) }) .catch((err)=>{ res.status(500).json(err.message) }) } else { res.status(400).send("Empty query!") } })*/ app.get("/song/:songname", (req, res) => { db.query( "select * from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname], (error, results) => { if (error) { res.status(500).json(error.message); } else { //console.log(req.params.songname+":"+JSON.stringify(results.rows)); res.status(200).json(results.rows); } } ); }); app.get("/songs", (req, res) => { db.query( "select songs.*,songdata.rating as rating,songdata.difficulty,songdata.notecount from songs left join songdata on songs.id=songdata.songid", (error, results) => { if (error) { res.status(500).json(error.message); } else { //console.log(req.params.songname+":"+JSON.stringify(results.rows)); //res.status(200).json(results.rows) var data = {}; results.rows.forEach((song) => { if (data[song.id]) { if (typeof data[song.id].rating === "string") { var oldRating = data[song.id].rating; var oldNoteCount = data[song.id].notecount; data[song.id].rating = {}; data[song.id].notecount = {}; data[song.id].rating[data[song.id].difficulty] = oldRating; data[song.id].notecount[data[song.id].difficulty] = oldNoteCount; } data[song.id].rating[song.difficulty] = song.rating; data[song.id].notecount[song.difficulty] = song.notecount; } else { data[song.id] = song; if (data[song.id].rating === null) { data[song.id].rating = {}; } if (data[song.id].notecount === null) { data[song.id].notecount = {}; } } }); res.status(200).json(data); } } ); }); /*app.post('/register', (req, res) => { if (req.body && req.body.username && req.body.username.length>2 && req.body.email) { var duplicateFound=false; db.query('select * from users where username=$1 limit 1',[req.body.username]) .then((data)=>{ if (data.rows.length>0) { throw new Error("User "+data.rows[0].username+" already exists!"); } else { return db.query('insert into users(username,email) values($1,$2) returning username,email',[req.body.username,req.body.email]) } }) .then((data)=>{res.status(200).json(data.rows)}) .catch((err)=>{res.status(500).json(err.message)}) } else { res.status(400).json("Invalid username!") } })*/ app.delete("/remove", (req, res) => { if ( req.body && req.body.username !== undefined && req.body.authentication_token !== undefined && req.body.playid !== undefined ) { var userObj = {}, songObj = {}, rating = 0, isFirstClear = false; db.query( "select id,authentication_token,playcount,fccount,cool,fine,safe,sad,worst,eclear,nclear,hclear,exclear,exexclear from users where username=$1 limit 1", [req.body.username] ) .then((data) => { if (data && data.rows.length > 0) { userObj = data.rows[0]; if (req.body.authentication_token === userObj.authentication_token) { return db.query( "delete from plays where id=$1 and userid=$2 returning *", [req.body.playid, userObj.id] ); } else { throw new Error("Could not authenticate user!"); } } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (data && data.rows.length > 0) { songObj = data.rows[0]; return CalculateRating(req.body.username); } else { throw new Error("Could not find play!"); } }) .then((data) => { rating = data; return db.query( "select * from plays where songid=$1 and userid=$2 and difficulty=$3 and score>0 limit 1", [songObj.songid, userObj.id, songObj.difficulty] ); }) .then((data) => { if (data && data.rows.length === 0) { isFirstClear = true; } /*console.log([data,userObj.playcount-1,(songObj.safe==0&&songObj.sad==0&&songObj.worst==0)?userObj.fccount-1:userObj.fccount,userObj.cool-songObj.cool,userObj.fine-songObj.fine,userObj.safe-songObj.safe,userObj.sad-songObj.sad,userObj.worst-songObj.worst,(songObj.difficulty=="E")?userObj.ecount-1:userObj.ecount,(songObj.difficulty=="N")?userObj.ncount-1:userObj.ncount,(songObj.difficulty=="H")?userObj.hcount-1:userObj.hcount,(songObj.difficulty=="EX")?userObj.excount-1:userObj.excount,(songObj.difficulty=="EXEX")?userObj.exexcount-1:userObj.exexcount]);*/ return db.query( "update users set rating=$1,playcount=$2,fccount=$3,cool=$4,fine=$5,safe=$6,sad=$7,worst=$8,eclear=$9,nclear=$10,hclear=$11,exclear=$12,exexclear=$13 where id=$14 returning rating,playcount,fccount,cool,fine,safe,sad,worst,eclear,nclear,hclear,exclear,exexclear", [ rating, userObj.playcount - 1, songObj.safe == 0 && songObj.sad == 0 && songObj.worst == 0 ? userObj.fccount - 1 : userObj.fccount, userObj.cool - songObj.cool, userObj.fine - songObj.fine, userObj.safe - songObj.safe, userObj.sad - songObj.sad, userObj.worst - songObj.worst, songObj.difficulty == "E" && isFirstClear ? userObj.eclear - 1 : userObj.eclear, songObj.difficulty == "N" && isFirstClear ? userObj.nclear - 1 : userObj.nclear, songObj.difficulty == "H" && isFirstClear ? userObj.hclear - 1 : userObj.hclear, songObj.difficulty == "EX" && isFirstClear ? userObj.exclear - 1 : userObj.exclear, songObj.difficulty == "EXEX" && isFirstClear ? userObj.exexclear - 1 : userObj.exexclear, userObj.id, ] ); }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json({ user: data.rows[0], song: songObj }); axios.post("http://projectdivar.com/updates/" + userObj.id, { password: process.env.GMAIL, type: "delete", }); } else { throw new Error( "Could not update user information, but song is deleted!" ); } }) .catch((err) => { res.status(500).json(err.message); }); } else { res.status(400).send("Missing required parameters!"); } }); app.post("/upload", function (req, res) { if ( !req.files || Object.keys(req.files).length === 0 || req.body.username === undefined || req.body.authentication_token === undefined ) { res.status(400).send("No files were uploaded. Invalid parameters."); return; } let file = req.files.file; //console.log(file) if (file.size > 15 * 1024 * 1024) { res .status(400) .send( "File is too large! Max is 15MB! Consider splitting your plays into chunks (Recommended 50 files per zip)." ); return; } if ( file.mimetype !== "application/x-zip-compressed" && file.mimetype !== "image/jpeg" && file.mimetype !== "image/png" && file.mimetype !== "application/octet-stream" ) { return; } var uploads = 0; var userId = -1; var fileLoc = ""; var count = 0; db.query( "select uploads,id from users where username=$1 and authentication_token=$2", [req.body.username, req.body.authentication_token] ) .then((data) => { if (data.rows.length > 0) { uploads = data.rows[0].uploads; userId = data.rows[0].id; fileLoc = "files/plays/" + userId + "/" + uploads; return file.mv(fileLoc); } else { throw new Error( "Could not find / authenticate user with name " + req.body.username + "!" ); } }) .then((data) => { if (file.mimetype !== "application/x-zip-compressed") { return db.query("update users set uploads=$1 where username=$2", [ Number(uploads) + 1, req.body.username, ]); } else { //console.log(uploads) uploads++; //console.log(uploads) return {}; } }) .then((data) => { if (file.mimetype !== "application/x-zip-compressed") { if (req.body.playid !== undefined) { //Add the url to that play. return db.query("update plays set src=$1 where id=$2 and userid=$3", [ "http://projectdivar.com/" + fileLoc, Number(req.body.playid), userId, ]); } else { return axios.post("http://projectdivar.com/image", { url: "http://projectdivar.com/" + fileLoc, user: req.body.username, auth: req.body.authentication_token, }); } } else { //This is a zip file. var promises = []; promises.push( new Promise((resolve) => { var stream = fs.createReadStream(fileLoc); stream .pipe(unzipper.Parse()) .on("entry", function (entry) { const fileName = entry.path; const type = entry.type; // 'Directory' or 'File' const size = entry.vars.uncompressedSize; // There is also compressedSize; if ( type == "File" && (fileName.includes(".jpg") || fileName.includes(".jpeg") || fileName.includes(".png")) ) { promises.push( new Promise((resolve) => { var file = "files/plays/" + userId + "/" + uploads++; entry .pipe(fs.createWriteStream(file)) .on("finish", () => { /*console.log("Promise finished!");*/ count++; resolve({ file: "http://projectdivar.com/" + file }); }); }).then((data) => { return db.query( "insert into uploadedplays values($1,$2,$3)", [data.file, userId, new Date()] ); }) ); } else { entry.autodrain(); } }) .on("finish", () => { /*console.log("Read finished");*/ resolve(); }); }) ); setTimeout(() => console.dir(promises), 5000); return Promise.all(promises); } }) .then((data) => { if (file.mimetype !== "application/x-zip-compressed") { res.status(200).send("Your play has been submitted! Thank you."); } else { //console.log(data) //console.log(uploads)) fs.unlink(fileLoc, (err) => { }); return db.query("update users set uploads=$1 where username=$2", [ Number(uploads) + 1, req.body.username, ]); } }) .then((data) => { if (file.mimetype !== "application/x-zip-compressed") { } else { res .status(200) .send( "Submitted " + count + " plays to the submission system. They will be processed shortly! Thank you." ); } }) .catch((err) => { console.log(err.message); res.status(500).send(err.message); }); }); app.post("/submit", (req, res) => { function addToQueue(src, userid, songid) { if (src) { db.query("select * from uploadedplays where filename=$1", [ req.body.src, ]).then((data) => { if (data.rows.length > 0) { if (data.rows[0].tries > -5) { db.query( "update uploadedplays set filename=$1,userid=$2,submissiondate=$3,id=$4,tries=$5 returning *", [src, userid, moment(), songid, data.rows[0].tries * -1 + 1] ).then((data) => { if (data.rows.length > 0) { console.log( "Added back to queue: " + JSON.stringify(data.rows[0]) ); } }); } } }); } } if ( req.body && req.body.username !== undefined && req.body.authentication_token !== undefined && req.body.song !== undefined && req.body.difficulty !== undefined && req.body.cool !== undefined && req.body.fine !== undefined && req.body.safe !== undefined && req.body.sad !== undefined && req.body.worst !== undefined && req.body.fail !== undefined && req.body.percent !== undefined ) { if ( req.body.cool == -1 || req.body.fine == -1 || req.body.safe == -1 || req.body.sad == -1 || req.body.worst == -1 ) { fs.writeFileSync("invalidSongs", JSON.stringify(req.body) + "\n", { flag: "a", }); res.status(400).json("Invalid note parameters!"); } var fail = true; if (req.body.fail !== undefined) { fail = req.body.fail == "true"; //console.log("Fail is "+fail+" type:"+typeof(fail)) } var submitDate = new Date(); if (req.body.submitDate !== undefined) { submitDate = req.body.submitDate; } var playstyle = "", songsubmitdata = {}, mod = "", combo = -1, gameScore = -1, isFC = false, songRating = -1, userId = -1, songId = -1, playcount = -1, fccount = -1, cool = -1, fine = -1, safe = -1, sad = -1, worst = -1, alreadyPassed = false, eclear = -1, nclear = -1, hclear = -1, exclear = -1, exexclear = -1; var songdata = {}, userObj = {}; if (req.body.mod !== undefined) { mod = req.body.mod; } if (req.body.combo !== undefined) { combo = req.body.combo; } if (req.body.gameScore !== undefined) { gameScore = req.body.gameScore; } db.query( "select id,authentication_token,playcount,fccount,cool,fine,safe,sad,worst,eclear,nclear,hclear,exclear,exexclear,playstyle,megamix,futuretone from users where username=$1 limit 1", [req.body.username] ) .then((data) => { if (data && data.rows.length > 0) { if ( data.rows[0].authentication_token === req.body.authentication_token ) { var obj = data.rows[0]; userObj = data.rows[0]; playstyle = data.rows[0].playstyle; eclear = obj.eclear; nclear = obj.nclear; hclear = obj.hclear; exclear = obj.exclear; exexclear = obj.exexclear; cool = data.rows[0].cool; fine = data.rows[0].fine; safe = data.rows[0].safe; sad = data.rows[0].sad; worst = data.rows[0].worst; fccount = data.rows[0].fccount; playcount = data.rows[0].playcount; userId = data.rows[0].id; return db.query( "select id,mega39s,futuretone from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.body.song] ); } else { throw new Error("Could not authenticate!"); } } else { console.log(req.body); throw new Error("Could not find user."); } }) .then((data) => { if (data && data.rows.length > 0) { songId = data.rows[0].id; songdata = data.rows[0]; if ( !( req.body.difficulty === "H" || req.body.difficulty === "N" || req.body.difficulty === "E" || req.body.difficulty === "EX" || req.body.difficulty === "EXEX" ) ) { throw new Error("Invalid difficulty!"); } if ( req.body.cool == -1 || req.body.fine == -1 || req.body.safe == -1 || req.body.sad == -1 || req.body.worst == -1 ) { throw new Error("Invalid submission!"); } return db.query( "select rating from songdata where songid=$1 and difficulty=$2 limit 1", [songId, req.body.difficulty] ); } else { throw new Error("Could not find song."); } }) .then((data) => { songRating = data.rows[0].rating; return db.query( "select id from plays where userid=$1 and score>0 and difficulty=$2 and songid=$3 limit 1", [userId, req.body.difficulty, songId] ); }) .then((data) => { if (data && data.rows.length > 0) { alreadyPassed = true; /*console.log(data);*/ } var score = CalculateSongScore({ rating: songRating, cool: req.body.cool, fine: req.body.fine, safe: req.body.safe, sad: req.body.sad, worst: req.body.worst, percent: req.body.percent, difficulty: req.body.difficulty, fail: fail, }); return db.query( "insert into plays(songId,userId,difficulty,cool,fine,safe,sad,worst,percent,date,score,fail,mod,combo,gamescore,src,playstyle) values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) returning *", [ songId, userId, req.body.difficulty, req.body.cool, req.body.fine, req.body.safe, req.body.sad, req.body.worst, req.body.percent, submitDate, score, fail, mod, combo, gameScore, req.body.src ? req.body.src : "", playstyle ? playstyle : "", ] ); }) .then((data) => { if (data && data.rows.length > 0) { songsubmitdata = data.rows[0]; //console.log(alreadyPassed+" / "+typeof(alreadyPassed)) if (alreadyPassed === false && songsubmitdata.score > 0) { switch (req.body.difficulty) { case "E": { eclear++; } break; case "N": { nclear++; } break; case "H": { hclear++; } break; case "EX": { exclear++; } break; case "EXEX": { exexclear++; } break; } } isFC = songsubmitdata.safe === 0 && songsubmitdata.sad === 0 && songsubmitdata.worst === 0; return CalculateRating(req.body.username); } else { throw new Error("Could not submit song."); } }) .then((data) => { return db.query( "update users set rating=$1,last_played=$3,playcount=$4,fccount=$5,cool=$6,fine=$7,safe=$8,sad=$9,worst=$10,eclear=$11,nclear=$12,hclear=$13,exclear=$14,exexclear=$15,megamix=$16,futuretone=$17 where username=$2", [ data, req.body.username, new Date(), ++playcount, fccount + (isFC ? 1 : 0), cool + Number(req.body.cool), fine + Number(req.body.fine), safe + Number(req.body.safe), sad + Number(req.body.sad), worst + Number(req.body.worst), eclear, nclear, hclear, exclear, exexclear, songdata.mega39s || userObj.megamix, (songdata.futuretone && !songdata.mega39s) || userObj.futuretone, ] ); }) .then((data) => { return songsubmitdata; }) .then((data) => { //userId //password, type axios.post("http://projectdivar.com/updates/" + userId, { password: process.env.GMAIL, type: "submit", }); if (req.body.src) { db.query("delete from uploadedplays where filename=$1", [ req.body.src, ]); } res.status(200).json(data); }) .catch((err) => { //console.log(req.body); console.log(err); addToQueue(req.body.src, userId, songId); res.status(500).send(err.message); }); } else { console.log(req.body); res.status(400).send("Missing required parameters!"); } }); CalculateSongScore = (song) => { if (song.fail == true) { return 0; } var noteCount = Number(song.cool) + Number(song.fine) + Number(song.safe) + Number(song.sad) + Number(song.worst); var comboBreaks = Number(song.safe) + Number(song.sad) + Number(song.worst); /*console.log("Combo Breaks: "+comboBreaks) console.log("Is FC? "+(comboBreaks===0)) console.log("Is PFC? "+(song.fine===0&&song.safe===0&&song.sad===0&&song.worst===0))*/ var scoreMult = 1; var percentMult = 1; if (song.percent > 110) { song.percent = 100; } if ( song.fine === 0 && song.safe === 0 && song.sad === 0 && song.worst === 0 ) { scoreMult = 2.4; } else if (comboBreaks === 0) { scoreMult = 1.4; } else { scoreMult = 1; } switch (song.difficulty) { case "E": { if (song.percent < 30) { percentMult = 0; } else { percentMult = 1 + Math.pow(1.0 * ((song.percent - 30) / 70.0), 3); } } break; case "N": { if (song.percent < 50) { percentMult = 0; } else { percentMult = 1 + Math.pow(1.0 * ((song.percent - 50) / 50.0), 3); } } break; case "H": { if (song.percent < 60) { percentMult = 0; } else { percentMult = 1 + Math.pow(1.0 * ((song.percent - 60) / 40.0), 3); } } break; case "EX": case "EXEX": { if (song.percent < 70) { percentMult = 0; } else { percentMult = 1 + Math.pow(1.0 * ((song.percent - 70) / 30.0), 3); } } break; default: { if (song.percent < 60) { percentMult = 0; } else { percentMult = 1 + Math.pow(1.0 * ((song.percent - 60) / 40.0), 3); } } } /*console.log("Score mult: "+scoreMult) console.log("Percent mult: "+percentMult)*/ var score = ((song.cool * 100 + song.fine * 50 + song.safe * 10 + song.sad * 5) / 1000.0) * percentMult * scoreMult; if (scoreMult > 0 && percentMult > 0) { score += Math.pow(song.rating, 3) / 5; } return Number(score); }; CalculateAccuracy = (cool, fine, safe, sad, worst) => { var noteCount = cool + fine + safe + sad + worst; var sum = cool + fine * 0.5 + safe * 0.1 + sad * 0.05; return sum / noteCount; }; CalculateRating = (username) => { var songs = []; var debugScoreList = ""; var userId = -1; /* //Old rating algorithm. return db.query('select id from users where username=$1',[username]) .then((data)=>{if(data.rows.length>0){userId=data.rows[0].id;return db.query('select * from plays where userid=$1 order by score desc limit 100',[userId])}else{return 0}}) .then((data)=>{if(data.rows.length>0){return data.rows.reduce((sum,song,i)=>{ return sum+Number(CalculateSongScore(song)*(Math.pow(0.8,i)))},0)}else{return 0}}) .catch((err)=>{throw new Error(err.message)})*/ return db .query("select id from users where username=$1 limit 1", [username]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query("select * from songs order by id asc"); } else { return 0; } }) .then((data) => { if (data.rows.length > 0) { songs = data.rows; return Promise.all( data.rows.map((song) => { return db .query( "select * from plays where userId=$1 and songId=$2 and score!=$3 order by score desc limit 100", [userId, song.id, "NaN"] ) .then((data) => { if (data.rows.length > 0) { debugScoreList += song.name + "\n"; songs[song.id - 1].score = data.rows.reduce( (sum, play, i) => { debugScoreList += " " + play.score + " -> " + play.score * Math.pow(0.2, i) + ""; if ( i === 0 && play.fine + play.safe + play.sad + play.worst === 0 ) { songs[play.songid - 1].pfc = true; debugScoreList += "+"; } else if ( i === 0 && play.safe + play.sad + play.worst === 0 ) { songs[play.songid - 1].fc = true; debugScoreList += "*"; } debugScoreList += "\n"; /*console.log("Play score:"+play.score+". Sum:"+sum);*/ return ( sum + play.score * Math.pow(0.2, i) ); }, 0 ); debugScoreList += " " + songs[song.id - 1].score + "\n"; } }); }) ); } }) .then(() => { return songs .sort((a, b) => { var scorea = a.score ? a.score : 0; var scoreb = b.score ? b.score : 0; return scorea > scoreb ? -1 : 1; }) .reduce((sum, song, i) => { if (song.score && !isNaN(song.score)) { debugScoreList += song.name + ": " + song.score + " -> " + song.score * Math.pow(0.9, i) + (song.pfc ? "+" + 2 : song.fc ? "+" + 1 : 0) + "\n"; return ( sum + song.score * Math.pow(0.9, i) + (song.pfc ? 2 : song.fc ? +1 : 0) ); } else { return sum; } }, 0); }) .then((data) => { /*console.log(debugScoreList);*/ return data; }); }; app.get("/songdiffs", (req, res) => { var diffObj = {}; db.query("select COUNT(*) from songdata where difficulty='E'") .then((data) => { diffObj.E = data.rows[0].count; return db.query("select COUNT(*) from songdata where difficulty='N'"); }) .then((data) => { diffObj.N = data.rows[0].count; return db.query("select COUNT(*) from songdata where difficulty='H'"); }) .then((data) => { diffObj.H = data.rows[0].count; return db.query("select COUNT(*) from songdata where difficulty='EX'"); }) .then((data) => { diffObj.EX = data.rows[0].count; return db.query("select COUNT(*) from songdata where difficulty='EXEX'"); }) .then((data) => { diffObj.EXEX = data.rows[0].count; res.status(200).json(diffObj); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/accuracy/:username", (req, res) => { db.query("select cool,fine,safe,sad,worst from users where username=$1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { return CalculateAccuracy( data.rows[0].cool, data.rows[0].fine, data.rows[0].safe, data.rows[0].sad, data.rows[0].worst ); } else { throw new Error("User does not exist!"); } }) .then((data) => { res.status(200).json({ accuracy: data }); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/updates/:userid", (req, res) => { db.query("select * from userupdate where userid=$1", [req.params.userid]) .then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows[0]); } else { res.status(400).send("No user update found!"); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.post("/updates/:userid", (req, res) => { if (req.body && req.body.password && req.body.type) { if (req.body.password === process.env.GMAIL) { db.query( "insert into userupdate(userid,update_type,date) values($1,$2,$3) on conflict(userid) do update set update_type=$2,date=$3 where userupdate.userid=$1 returning *", [req.params.userid, req.body.type, moment()] ) .then((data) => { if (data.rows.length >= 0) { res.status(200).send(data.rows[0]); } else { throw new Error("Could not update user."); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(403).send("Could not authenticate"); } } else { res.status(400).send("Invalid credentials!"); } }); app.get("/recalculatescore/:playid", (req, res) => { var userId = -1; var username = null; var songRating = -1; var song; db.query("select * from plays where id=$1 limit 1", [req.params.playid]) .then((data) => { if (data.rows.length > 0) { song = data.rows[0]; userId = song.userid; /*console.log(song);*/ return db.query( "select rating from songdata where songid=$1 and difficulty=$2 limit 1", [song.songid, song.difficulty] ); } else { throw new Error("This play does not exist!"); } }) .then((data) => { if (data.rows.length > 0) { songRating = data.rows[0].rating; var score = CalculateSongScore({ rating: songRating, cool: song.cool, fine: song.fine, safe: song.safe, sad: song.sad, worst: song.worst, percent: song.percent, difficulty: song.difficulty, fail: song.fail, }); return db.query("update plays set score=$1 where id=$2 returning *", [ score, req.params.playid, ]); } else { throw new Error("Failed to retrieve song data!"); } }) .then((data) => { //console.log(data); if (data.rows.length > 0) { var scoreData = data.rows[0]; return db .query("select username from users where id=$1", [userId]) .then((data) => { username = data.rows[0].username; return CalculateRating(username); }) .then((data) => { db.query("update users set rating=$1 where username=$2", [ data, username, ]); }) .then(() => { return scoreData; }) .catch((err) => { throw new Error("Could not update score"); }); } else { throw new Error("Failed to update score!"); } }) .then((data) => { res.status(200).json(data); axios.post("http://projectdivar.com/updates/" + userId, { password: process.env.GMAIL, type: "recalculate", }); }) .catch((err) => { console.log(err); res.status(500).send(err.message); }); }); /* app.get('/playdata',(req,res)=>{ db.query('select * from plays') .then((data)=>{res.status(200).json(data.rows)}) .catch((err)=>res.status(500).json(err.message)) })*/ app.post("/recalculatePlayerData/:id", (req, res) => { if (req.body && req.body.password) { if (req.body.password === process.env.GMAIL) { db.query( " select COUNT(*) as plays,SUM(cool) as coolsum,SUM(fine) as finesum,SUM(safe) as safesum,SUM(sad) as sadsum,SUM(worst) as worstsum,COUNT(*) filter(where safe=0 and sad=0 and worst=0) as fc from plays where userid=$1", [req.params.id] ) .then((data) => { var d = data.rows[0]; return db.query( "update users set playcount=$1,fccount=$2,cool=$3,fine=$4,safe=$5,sad=$6,worst=$7 where id=$8 returning playcount,fccount,cool,fine,safe,sad,worst,id", [ d.plays, d.fc, d.coolsum !== null ? d.coolsum : 0, d.finesum !== null ? d.finesum : 0, d.safesum !== null ? d.safesum : 0, d.sadsum !== null ? d.sadsum : 0, d.worstsum !== null ? d.worstsum : 0, req.params.id, ] ); }) .then((data) => { res.status(200).json(data.rows[0]); }); } } }); app.get("/completionreport/:username", (req, res) => { //Number of passes,plays,fcs,pfcs, and the best play. var userId = -1, songs, promises = []; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query("select * from songs order by id asc"); } else { throw new Error("Cannot find user!"); } }) .then((data) => { songs = data.rows; songs.forEach((song) => { promises.push( db .query( "select * from (select userid,count(*) filter(where difficulty='E' and mod='SD' and score>0) as ESDCount,count(*) filter(where difficulty='N' and mod='SD' and score>0) as NSDCount,count(*) filter(where difficulty='H' and mod='SD' and score>0) as HSDCount,count(*) filter(where difficulty='EX' and mod='SD' and score>0) as EXSDCount,count(*) filter(where difficulty='EXEX' and mod='SD' and score>0) as EXEXSDCount,count(*) filter(where difficulty='E' and mod='HD' and score>0) as EHDCount,count(*) filter(where difficulty='N' and mod='HD' and score>0) as NHDCount,count(*) filter(where difficulty='H' and mod='HD' and score>0) as HHDCount,count(*) filter(where difficulty='EX' and mod='HD' and score>0) as EXHDCount,count(*) filter(where difficulty='EXEX' and mod='HD' and score>0) as EXEXHDCount,count(*) filter(where difficulty='E' and mod='HS' and score>0) as EHSCount,count(*) filter(where difficulty='N' and mod='HS' and score>0) as NHSCount,count(*) filter(where difficulty='H' and mod='HS' and score>0) as HHSCount,count(*) filter(where difficulty='EX' and mod='HS' and score>0) as EXHSCount,count(*) filter(where difficulty='EXEX' and mod='HS' and score>0) as EXEXHSCount,Count(*) filter(where difficulty='E' and score>0) as EClearCount,Count(*) filter(where difficulty='N' and score>0) as NClearCount,Count(*) filter(where difficulty='H' and score>0) as HClearCount,Count(*) filter(where difficulty='EX' and score>0) as EXClearCount,Count(*) filter(where difficulty='EXEX' and score>0) as EXEXClearCount,count(*) filter(where difficulty='E') as ECount,count(*) filter(where difficulty='N') as NCount,count(*) filter(where difficulty='H') as HCount,count(*) filter(where difficulty='EX') as EXCount,count(*) filter(where difficulty='EXEX') as EXEXCount,Count(*) filter(where safe=0 and sad=0 and worst=0 and difficulty='E') as EFCCount,Count(*) filter(where safe=0 and sad=0 and worst=0 and difficulty='N') as NFCCount,Count(*) filter(where safe=0 and sad=0 and worst=0 and difficulty='H') as HFCCount,Count(*) filter(where safe=0 and sad=0 and worst=0 and difficulty='EX') as EXFCCount,Count(*) filter(where safe=0 and sad=0 and worst=0 and difficulty='EXEX') as EXEXFCCount,Count(*) filter(where fine=0 and safe=0 and sad=0 and worst=0 and difficulty='E') as EPFCCount,Count(*) filter(where fine=0 and safe=0 and sad=0 and worst=0 and difficulty='N') as NPFCCount,Count(*) filter(where fine=0 and safe=0 and sad=0 and worst=0 and difficulty='H') as HPFCCount,Count(*) filter(where fine=0 and safe=0 and sad=0 and worst=0 and difficulty='EX') as EXPFCCount,Count(*) filter(where fine=0 and safe=0 and sad=0 and worst=0 and difficulty='EXEX') as EXEXPFCCount from plays where userid=$1 and songid=$2 group by userid)t1 join (select rank,t.score,t.percent from (select row_number()over(order by score desc)rank,* from(select distinct on (userid) * from (select * from plays where songid=$2)t order by userid,score desc)t)t where userid=$1)t2 on t1.userid=t1.userid", [userId, song.id] ) .then((data) => { if (data.rows.length > 0) { song.report = data.rows[0]; } else { song.report = { ecount: 0, ncount: 0, hcount: 0, excount: 0, exexcount: 0, efccount: 0, nfccount: 0, hfccount: 0, exfccount: 0, exexfccount: 0, epfccount: 0, npfccount: 0, hpfccount: 0, expfccount: 0, exexpfccount: 0, rank: 0, }; } }) ); }); return Promise.all(promises); }) .then((data) => { return res.status(200).json(songs); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/ratings/:songname/:username", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Could not find user!"); } }) .then((data) => { if (data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from (select row_number()over(order by score desc)rank,* from(select distinct on (userid) * from (select * from plays where songid=$1)t order by userid,score desc)t)t where userid=$2", [songId, userId] ); } else { throw new Error("Could not find song!"); } }) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/bestplays/:username", (req, res) => { var songId = -1, userId = -1, songPromises = [], plays = [], limit = 5, offset = 0; if (req.query.limit) { limit = req.query.limit; } if (req.query.offset) { offset = req.query.offset; } db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select * from (select distinct on (songid) * from plays where userid=$1 " + (req.query.fails === "false" ? "and score!=0" : "") + " order by songid,score desc)t order by score desc limit $2 offset $3", [userId, Number(limit), Number(offset)] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/bestplay/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; if (req.params.songname) { return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { return db.query( "select * from plays where userid=$1 order by score desc", [userId] ); } } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from plays where userid=$1 and songid=$2 and difficulty=$3 order by score desc,percent desc limit 1", [userId, songId, req.params.difficulty] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json(data.rows[0]); } else { res.status(400).send("No data found!"); } }) .catch((err) => { res.status(500).send(err.message + JSON.stringify(req.body)); }); }); app.get("/userdata/:username", (req, res) => { var songId = -1, userId = -1, finalData = {}; db.query( "select id,megamix,futuretone,playstyle,playcount,fccount,rating,last_played,cool,fine,safe,sad,worst,eclear,nclear,hclear,exclear,exexclear from users where username=$1 limit 1", [req.params.username] ) .then((data) => { if (data && data.rows.length > 0) { finalData = data.rows[0]; return db.query( "select t.difficulty,COUNT(t.difficulty) from (select distinct on(songid) songid,*,users.id from plays join users on userid=users.id where users.username=$1 and plays.safe=0 and plays.worst=0 and plays.sad=0)t group by t.difficulty", [req.params.username] ); } else { throw new Error("Could not retrieve user data!"); } }) .then((data) => { if (data) { var fcData = {}; data.rows.forEach((fc) => { fcData[fc.difficulty] = fc.count; }); finalData = { ...{ fcdata: fcData }, ...finalData }; return db.query( "select t.difficulty,COUNT(t.difficulty) from (select distinct on(songid) songid,*,users.id from plays join users on userid=users.id where users.username=$1 and plays.fine=0 and plays.safe=0 and plays.worst=0 and plays.sad=0)t group by t.difficulty", [req.params.username] ); } else { throw new Error("Could not retrieve user data!"); } }) .then((data) => { if (data) { var fcData = {}; data.rows.forEach((fc) => { fcData[fc.difficulty] = fc.count; }); finalData = { ...{ pfcdata: fcData }, ...finalData }; res.status(200).json(finalData); } else { throw new Error("Could not retrieve user data!"); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/plays/:username/:songid", (req, res) => { var limit = 5, offset = 0; if (req.query.limit) { limit = req.query.limit; } if (req.query.offset) { offset = req.query.offset; } db.query( "select plays.* from plays join users on users.id=plays.userid where users.username=$1 and plays.songid=$2 order by score desc,percent desc,date desc limit $3 offset $4", [req.params.username, req.params.songid, Number(limit), Number(offset)] ) .then((data) => { res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/playcount/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from plays where userid=$1 and songid=$2 and difficulty=$3 order by score desc", [userId, songId, req.params.difficulty] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json({ playcount: data.rows.length }); } else { res.status(200).json({ playcount: 0 }); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/songpasscount/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from plays where userid=$1 and songid=$2 and difficulty=$3 and score>0", [userId, songId, req.params.difficulty] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json({ passcount: data.rows.length }); } else { res.status(200).json({ passcount: 0 }); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/songfccount/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from plays where userid=$1 and songid=$2 and difficulty=$3 and safe=0 and sad=0 and worst=0", [userId, songId, req.params.difficulty] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json({ fccount: data.rows.length }); } else { res.status(200).json({ fccount: 0 }); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/songpfccount/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select * from plays where userid=$1 and songid=$2 and difficulty=$3 and fine=0 and safe=0 and sad=0 and worst=0", [userId, songId, req.params.difficulty] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { res.status(200).json({ fccount: data.rows.length }); } else { res.status(200).json({ fccount: 0 }); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/songmods/:username/:songname/:difficulty", (req, res) => { var songId = -1, userId = -1, hs = 0, sd = 0, hd = 0; db.query("select id from users where username=$1 limit 1", [ req.params.username, ]) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; return db.query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [req.params.songname] ); } else { throw new Error("Cannot find user!"); } }) .then((data) => { if (req.params.songname && data.rows.length > 0) { songId = data.rows[0].id; return db.query( "select COUNT(mod) from (select * from plays where userid=$1 and songid=$2 and difficulty=$3 and mod=$4)t", [userId, songId, req.params.difficulty, "HS"] ); } else { res.status(400).send("Could not find song!"); } }) .then((data) => { if (data && data.rows.length > 0) { hs = data.rows[0].count; } return db.query( "select COUNT(mod) from (select * from plays where userid=$1 and songid=$2 and difficulty=$3 and mod=$4)t", [userId, songId, req.params.difficulty, "SD"] ); }) .then((data) => { if (data && data.rows.length > 0) { sd = data.rows[0].count; } return db.query( "select COUNT(mod) from (select * from plays where userid=$1 and songid=$2 and difficulty=$3 and mod=$4)t", [userId, songId, req.params.difficulty, "HD"] ); }) .then((data) => { if (data && data.rows.length > 0) { hd = data.rows[0].count; } res.status(200).json({ hs: hs, sd: sd, hd: hd }); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/rating/:username", (req, res) => { if (req.params.username) { db.query("select rating from users where username=$1 limit 1", [ req.params.username, ]).then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows[0]); } else { res.status(200).json({ rating: 0 }); } }); } else { res.status(400).send("Invalid username!"); } }); app.get("/recentplays/:username", (req, res) => { if (req.params.username) { db.query( "select plays.* from plays join users on users.id=plays.userid where users.username=$1 order by plays.id desc limit 5", [req.params.username] ).then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows); } else { res.status(200).json([]); } }); } else { res.status(400).send("Invalid username!"); } }); app.get("/users/:orderby/:sortorder", (req, res) => { if ( req.params.orderby && req.params.sortorder && req.query.limit && req.query.offset ) { var valid = ["rating", "last_played", "playcount", "username", "fccount"]; var validsort = ["desc", "asc"]; if ( valid.includes(req.params.orderby) && validsort.includes(req.params.sortorder) ) { db.query( "select username,rating,last_played,playcount,fccount from users order by " + req.params.orderby + " " + req.params.sortorder + ",rating desc limit $1 offset $2", [req.query.limit, req.query.offset] ) .then((data) => { return res.status(200).json(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Not a valid sort option!"); } } else { res.status(400).send("Invalid query!"); } }); function ValidateToken(username, token) { return db .query("select authentication_token from users where username=$1 limit 1", [ username, ]) .then((data) => { if (data.rows.length > 0) { return token === data.rows[0].authentication_token; } else { return false; } }); } function GetSongId(songname) { return db .query( "select id from songs where name=$1 or romanized_name=$1 or english_name=$1 limit 1", [songname] ) .then((data) => { if (data.rows.length > 0) { return data.rows[0].id; } else { throw new Error("Could not get song ID for song '" + songname + "'"); } }); } function GetNoteCount(songname, difficulty) { var songID = -1; return GetSongId(songname) .then((id) => { songID = id; return db.query( "select notecount from songdata where songid=$1 and difficulty=$2 limit 1", [songID, difficulty] ); }) .then((data) => { if (data.rows.length > 0) { return data.rows[0].notecount; } else { throw new Error( "Could not get note count for song '" + songname + "' on difficulty '" + difficulty + "'" ); } }); } app.post("/song/:songname/:difficulty", (req, res) => { if ( req.body && req.params.songname && req.params.difficulty && req.body.username && req.body.percent && req.body.authentication_token ) { var noteCount = 0, songID = 0, fail = false; ValidateToken(req.body.username, req.body.authentication_token) .then((allowed) => { if (allowed) { return GetSongId(req.params.songname); } else { throw new Error("Could not authenticate!"); } }) .then((songId) => { if (songId) { songID = songId; return GetNoteCount(req.params.songname, req.params.difficulty); } else { throw new Error("Could not find song ID!"); } }) .then((noteCount) => { var percentThreshold = (req.params.difficulty === "E" ? 100 : 107) / 100.0; var percent = (req.params.difficulty === "E" ? 100 : 107) ? Math.min(req.body.percent / 100.0, percentThreshold) : Math.min(req.body.percent / 107.0, percentThreshold); var cool = 0; var fine = 0; var safe = 0; var sad = 0; var worst = 0; for (var i = 0; i < noteCount; i++) { if (req.body.isFC) { if (Math.random() < percent) { cool++; } else { fine++; } } else { if (Math.random() < percent) { cool++; continue; } else if (Math.random() < percent) { fine++; continue; } else if (Math.random() < percent) { worst++; continue; } else if (Math.random() < percent) { safe++; continue; } else if (Math.random() < percent) { sad++; continue; } else { worst++; } } } switch (req.params.difficulty) { case "E": { if (req.body.percent < 30) { fail = true; } } break; case "N": { if (req.body.percent < 50) { fail = true; } } break; case "H": { if (req.body.percent < 60) { fail = true; } } break; case "EX": case "EXEX": { if (req.body.percent < 70) { fail = true; } } break; default: { if (req.body.percent < 60) { fail = true; } } break; } if (req.body.fail) { fail = req.body.fail; } //res.status(200).json({cool:cool,fine:fine,safe:safe,sad:sad,worst:worst,percent:req.body.percent}) return axios.post("http://projectdivar.com/submit", { username: req.body.username, authentication_token: req.body.authentication_token, song: req.params.songname, difficulty: req.params.difficulty, cool: cool, fine: fine, safe: safe, sad: sad, worst: worst, percent: req.body.percent, fail: String(fail), }); }) .then((data) => { res.status(200).json(data.data); }) .catch((err) => { res.status(400).send(err.message); }); } else { res.status(400).send("Invalid query!"); } }); function CheckUserExists(username, email) { return db.query( "select id,username,email,registered from users where username=$1 or email=$2 limit 1", [username, email] ); } function SendLoginEmail(username, emailTo, authCode) { const transporter = nodemailer.createTransport({ service: "gmail", auth: { user: "admin@projectdivar.com", pass: process.env.GMAIL, // naturally, replace both with your real credentials or an application-specific password }, }); transporter.sendMail( { from: "admin@projectdivar.com", to: emailTo, subject: "Project DivaR Login Code", html: `${username},

Thank you for using Project DivaR!

Your authentication code is ${authCode}!`, }, (err, info) => { if (err) { console.log(err.message); } else { console.log(info.envelope); console.log(info.messageId); } } ); } function SendRegistrationEmail(username, emailTo, authCode) { const transporter = nodemailer.createTransport({ service: "gmail", auth: { user: "admin@projectdivar.com", pass: process.env.GMAIL, // naturally, replace both with your real credentials or an application-specific password }, }); transporter.sendMail( { from: "admin@projectdivar.com", to: emailTo, subject: "Project DivaR Registration Code", html: `${username},

Thank you for signing up for Project DivaR!

Your authentication code is ${authCode}!`, }, (err, info) => { if (err) { console.log(err.message); } else { console.log(info.envelope); console.log(info.messageId); } } ); } function GetUserInfo(username) { return db.query( "select id,username,email,code_time from users where username=$1 limit 1", [username] ); } function GetUserLoginAllowed(username, authCode) { return db.query( "select id,username,email,code_time,playstyle,twitter_name,twitch_name from users where username=$1 and code=$2 limit 1", [username, authCode] ); } app.post("/authenticate/authToken", (req, res) => { if (req.body && req.body.username && req.body.authCode) { GetUserLoginAllowed(req.body.username.trim(), req.body.authCode.trim()) .then((data) => { if (data.rows.length > 0) { return db.query( "select authentication_token from users where id=$1", [data.rows[0].id] ); } else { return new Error("Failed login!"); } }) .then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows[0]); } else { return new Error("Failed to get authentication token!"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid Credentials!"); } }); app.post("/authenticate/login", (req, res) => { if (req.body && req.body.username && req.body.authCode) { GetUserLoginAllowed( req.body.username.trim(), req.body.authCode.trim() ).then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows[0]); } else { return new Error("Failed login!"); } }); } else { res.status(400).send("Invalid Credentials!"); } }); app.post("/sendemail/login", function (req, res) { if (req.body && req.body.username) { GetUserInfo(req.body.username.trim()) .then((data) => { if (data.rows.length > 0) { res.status(200).send("Email sent."); //console.log(data.rows[0].code_time) if (data.rows[0].code_time) { if ( moment(data.rows[0].code_time, "YYYY-MM-DD HH:mm:ss.SSSZ").diff( moment(), "minutes" ) <= -15 ) { var authCode = Math.floor(Math.random() * 90000) + 10000; SendLoginEmail(req.body.username, data.rows[0].email, authCode); db.query("update users set code=$1,code_time=$3 where id=$2", [ authCode, data.rows[0].id, moment(), ]); } //console.log(moment(data.rows[0].code_time,"YYYY-MM-DD HH:mm:ss.SSSZ").diff(moment(),'minutes')) } else { var authCode = Math.floor(Math.random() * 90000) + 10000; SendLoginEmail(req.body.username, data.rows[0].email, authCode); db.query("update users set code=$1,code_time=$3 where id=$2", [ authCode, data.rows[0].id, moment(), ]); } } else { return new Error("User does not exist!"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.patch("/updateRegisteredState", function (req, res) { if (req.body && req.body.username && req.body.authCode) { GetUserLoginAllowed(req.body.username, req.body.authCode) .then((data) => { if (data.rows.length > 0) { return db.query("update users set registered=$1 where id=$2", [ true, data.rows[0].id, ]); } else { throw new Error("Could not login!"); } }) .then((data) => { res.status(200).send("Registered!"); }) .catch((err) => { res.status(500).send("Could not finish registration!"); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/sendemail/register", function (req, res) { if (req.body && req.body.username && req.body.email) { //Generate a token for the user to login with. CheckUserExists(req.body.username.trim(), req.body.email.trim()) .then((data) => { var authCode = Math.floor(Math.random() * 90000) + 10000; var authenticationToken = String(Math.floor(Math.random() * 90000) + 10000) + "-" + String(Math.floor(Math.random() * 90000) + 10000) + "-" + String(Math.floor(Math.random() * 90000) + 10000); if (data.rows.length > 0) { //db.query("update users set code=$1 where id=$2",[authCode,data.rows[0].id]) if (data.rows[0].registered) { throw new Error("User already exists!"); } else { return db.query( "update users set code=$1 where id=$2 returning code", [authCode, data.rows[0].id] ); } } else { return db.query( "insert into users(username,email,authentication_token,code) values($1,$2,$3,$4) returning code", [req.body.username, req.body.email, authenticationToken, authCode] ); } }) .then((data) => { if (data.rows.length > 0) { res.status(200).send("Email sent."); SendRegistrationEmail( req.body.username, req.body.email, data.rows[0].code ); } else { throw new Error("Something bad happened!"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); function AuthenticateUser(username, auth) { return db.query( "select id,username,email from users where username=$1 and authentication_token=$2 limit 1", [username, auth] ); } app.post("/authenticateuser", function (req, res) { if (req.body && req.body.username && req.body.authenticationToken) { AuthenticateUser( req.body.username.trim(), req.body.authenticationToken.trim() ) .then((data) => { if (data.rows.length > 0) { res.status(200).send("Authentication Success!"); } else { throw new Error("Authentication Failed!"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/updateuser", function (req, res) { var userId = -1; if ( req.body && req.body.playStyle && req.body.username && req.body.authCode ) { GetUserLoginAllowed(req.body.username, req.body.authCode) .then((data) => { if (data.rows.length > 0) { userId = data.rows[0].id; if (req.body.twitchName) { db.query("update users set twitch_name=$1 where id=$2", [ req.body.twitchName, userId, ]); } if (req.body.twitterName) { return axios.get( "https://api.twitter.com/1.1/users/show.json?screen_name=" + req.body.twitterName, { headers: { /*BEARER*/ Authorization: "Bearer " + process.env.TWITTER_BEARER, //the token is a variable which holds the token }, } ); } else { return { data: {} }; } } else { throw new Error("Could not login!"); } }) .then((data) => { if (data.data.id) { return db.query( "update users set playstyle=$1,twitter=$2,twitter_name=$3 where id=$4", [req.body.playStyle, data.data.id, req.body.twitterName, userId] ); } else { return db.query("update users set playstyle=$1 where id=$2", [ req.body.playStyle, userId, ]); } }) .then((data) => { res.status(200).send("Successfully updated user settings!"); }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/getUserAuthData", function (req, res) { if (req.body && req.body.password && req.body.userId) { if (req.body.password === process.env.GUARDIANPASSWORD) { db.query( "select uploads,username,authentication_token from users where id=$1", [req.body.userId] ) .then((data) => { if (data.rows.length > 0) { res.status(400).json(data.rows[0]); db.query("update users set uploads=$1 where id=$2", [ data.rows[0].uploads + 1, req.body.userId, ]); } else { res.status(400).send("No user found!"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Authentication failed!"); } } else { res.status(400).send("Invalid credentials!"); } }); app.post("/passImageData", function (req, res) { if (req.body && req.body.user && req.body.auth && req.body.url) { axios .post("http://projectdivar.com/image", { user: req.body.user, auth: req.body.auth, url: req.body.url, }) .then((data) => { res.status(200).json(data.data); }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/streamstart/:id", function (req, res) { if (req.body && req.body.username && req.body.authentication_token) { GetUserLoginAllowed( req.body.username.trim(), req.body.authentication_token.trim() ) .then((data) => { if (data.rows.length > 0) { spawn("../divabotguardian/stream_monitor.sh", [ Number(req.params.id), ]); res.status(200).send("Monitor started"); } else { res.status(400).send("Failed to authenticate"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/streamtop/:id", function (req, res) { exec("ps 43", (err, out, stderr) => { if (err) { res.status(500).send(`${err.message}`); return; } if (stderr) { res.status(500).send(`${stderr}`); return; } res.status(200).send(`${out}`); }); }); app.post("/streaminfo/:id", function (req, res) { if (req.body && req.body.username && req.body.authentication_token) { GetUserLoginAllowed( req.body.username.trim(), req.body.authentication_token.trim() ) .then((data) => { if (data.rows.length > 0) { exec( "ps $(cat ../divabotguardian/processes/" + Number(req.params.id) + ".ffmpeg)|wc -l", (err, out, stderr) => { if (err) { res.status(500).send(`${err.message}`); return; } if (stderr) { res.status(500).send(`${stderr}`); return; } res.status(200).send(`${out}`); } ); } else { res.status(400).send("Failed to authenticate"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.get("/cans", function (req, res) { db.query( "select (select count(*) from cans where can=true) as cans,(select count(*) from cans where can=false) as notcan;" ) .then((data) => { res.status(200).send(data.rows[0]); }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/ev", function (req, res) { if (req.query.all) { db.query( "select * from " + (req.query.en ? "en_" : "") + "event order by id desc;" ) .then((data) => { res.status(200).send(data.rows); }) .catch((err) => { res.status(500).send(err.message); }); } else { db.query( "select * from " + (req.query.en ? "en_" : "") + "event order by id desc limit 1;" ) .then((data) => { res.status(200).send(data.rows[0]); }) .catch((err) => { res.status(500).send(err.message); }); } }); app.post("/cans", function (req, res) { if (req.body && req.body.cans !== undefined && req.body.cans !== null) { db.query("insert into cans(date,can) values($1,$2)", [ moment(), req.body.cans, ]).then((data) => { res.status(200).send("Done!"); }); } else { res.status(400).send("Invalid credentials!"); } }); app.post("/streamkill/:id", function (req, res) { if (req.body && req.body.username && req.body.authentication_token) { GetUserLoginAllowed( req.body.username.trim(), req.body.authentication_token.trim() ) .then((data) => { if (data.rows.length > 0) { exec( "kill $(cat ../divabotguardian/processes/" + Number(req.params.id) + ".ffmpeg)|wc -l", (err, out, stderr) => { if (err) { res.status(500).send(`${err.message}`); return; } if (stderr) { res.status(500).send(`${stderr}`); return; } res.status(200).send(`${out}`); } ); } else { res.status(400).send("Failed to authenticate"); } }) .catch((err) => { res.status(500).send(err.message); }); } else { res.status(400).send("Invalid credentials!"); } }); app.get("/streamdata/:id", function (req, res) { db.query("select twitch_name from users where id=$1", [req.params.id]) .then((data) => { return twitchStreams.get(data.rows[0].twitch_name); }) .then(function (streams) { if (streams.length > 0) { var streamchoice = undefined; for (var i = 0; i < streams.length; i++) { if (streams[i].quality.includes("720p")) { streamchoice = streams[i]; break; } } if (streamchoice === undefined) { streamchoice = streams[0]; } res.status(200).send(streamchoice.url); } else { res.status(400).send("Not online!"); } }) .catch((err) => { res.status(500).send(err.message); }); }); app.get("/eventchart", function (req, res) { const myChart = new QuickChart(); myChart .setConfig({ type: "bar", data: { labels: ["Hello world", "Foo bar"], datasets: [{ label: "Foo", data: [1, 2] }], }, }) .setWidth(300) .setHeight(150); // You can send the URL to someone... const chartImageUrl = myChart.getUrl(); res.status(200).send(''); }); var process_images = []; var processPromises = []; var largestId = 0; var filterId = 0; var MAX_INDEX = 12; //To prevent being rate-limited. function Process(data, ind) { for (var i in data.data.statuses) { var tweet = data.data.statuses[i]; if ( (tweet.source && tweet.source.includes("Nintendo Switch Share")) || tweet.source.includes("PlayStation®Network") ) { if (tweet.extended_entities) { //console.log(tweet.extended_entities.media) for (var j = 0; j < tweet.extended_entities.media.length; j++) { var media = tweet.extended_entities.media[j]; process_images.push({ image: media.media_url, user: tweet.user.id, id: tweet.id, }); } } } } //console.log(process_images) if (data.data.search_metadata.next_results && ind < MAX_INDEX) { return axios .get( "https://api.twitter.com/1.1/search/tweets.json" + data.data.search_metadata.next_results, { headers: { /*BEARER*/ Authorization: "Bearer " + process.env.TWITTER_BEARER, //the token is a variable which holds the token }, } ) .then((data) => { //console.log("Going to next: "+(ind+1)) return Process(data, ind + 1); }); } return "Done!"; } app.use( "/files", express.static("files", { maxAge: 86400000 * 30, }) ); /* axios.get('https://api.twitter.com/1.1/search/tweets.json?q=@divarbot', { headers: { Authorization: 'Bearer '+process.env.TWITTER_BEARER //the token is a variable which holds the token } }) .then((data)=>{ //console.log(data.data.statuses) //console.log(data.data) return Process(data); }) .then((data)=>{process_images.forEach((image)=>{console.log(image)})})*/ /*setInterval( ()=>{ twitchStreams.get('smallant') .then(function(streams) { //console.log(streams) if (streams.length>0) { db.query("update streams set stream=$1 where id=1",[streams[0].url]); } }) .catch((err)=>{ console.log(err.message) }) },5000)*/ /* setInterval( ()=>{ function addToQueue(uploadData) { if (uploadData.tries===undefined||uploadData.tries===null) { uploadData.tries=1; } else { uploadData.tries+=1; } if (uploadData.tries<5) { console.log("Failed to upload. Added back to queue. Tries: "+uploadData.tries+" / "+JSON.stringify(uploadData)) db.query("insert into uploadedplays(filename,userid,submissiondate,id,playid,tries) values($1,$2,$3,$4,$5,$6);", [uploadData.filename,uploadData.userid,uploadData.submissiondate,uploadData.id,uploadData.playid,uploadData.tries]) } } var uploadData=undefined,user=undefined,auth=undefined,playData; db.query("select * from uploadedplays where tries is null or tries>=0 order by submissiondate asc limit 1") .then((data)=>{ if (data.rows.length>0) { uploadData=data.rows[0]; //console.log(uploadData) return db.query("select username,authentication_token from users where id=$1",[uploadData.userid]) } }) .then((data)=>{ if (uploadData && data.rows.length>0) { user=data.rows[0].username auth=data.rows[0].authentication_token if (uploadData.tries!==undefined&&uploadData.tries!==null) { return db.query("update uploadedplays set tries=$2 where id=$1",[uploadData.id,(uploadData.tries*-1)]) } else { return db.query("update uploadedplays set tries=-1 where id=$1",[uploadData.id]) } } }) .then((data)=>{ if (uploadData) { //console.log(data.data) return axios.post("http://projectdivar.com/image", {url:uploadData.filename,user:user,auth:auth}) } }) .then((data)=>{ if (uploadData) { if (data.data==="Invalid parameters!") { throw new Error("Invalid parameters while trying to submit play!") } } }) .catch((err)=>{ if (uploadData) { addToQueue(uploadData) } }) } ,1000) */ //setInterval(()=>{db.query("select * from twitter_bot limit 1") //.then((data)=>{ // largestId=filterId=data.rows[0].lastpost; // //console.log("Filter Id: "+filterId); // /*return axios.get('https://api.twitter.com/1.1/search/tweets.json?q=%23mega39s', { // headers: { // Authorization: 'Bearer '+process.env.TWITTER_BEARER //the token is a variable which holds the token // } // })*/ // return axios.get('https://api.twitter.com/1.1/search/tweets.json?q=%23divarbot', { // headers: { // Authorization: 'Bearer '+process.env.TWITTER_BEARER //the token is a variable which holds the token // } // }) //}) //.then((data)=>{ // //console.log(data.data.statuses) // //console.log(data.data) // //data.data.s // //console.log("Reading Twitter Data...") // return Process(data,0); //}) //.then((data)=>{ // //console.log(process_images) // var promisesDone=0; // process_images.forEach((obj)=>{ // if (filterId{ // console.log("Process Twitter Post: "+obj.id); // return db.query("select id from users where twitter=$1",[obj.user]) // .then((data)=>{ // if (data.rows.length>0) { // console.log("Process new play for User id "+data.rows[0].id+"...") // return db.query("insert into uploadedplays values($1,$2,$3)",[obj.image,data.rows[0].id,new Date()]) // .then(()=>{resolve("Done!")}) // } else { // reject("Not associated with an Id!") // } // }) // .catch((err)=>{console.log(err.message);reject("Failed!")})})) // } //}) ////setTimeout(()=>{console.dir(processPromises, {'maxArrayLength': null})},2000) //return Promise.allSettled(processPromises) //}) //.then((data)=>{ // //console.log(largestId) // return db.query("update twitter_bot set lastpost=$1 returning *",[largestId]) //}) //.catch((err)=>{console.log(err.message)}) //},60000) // setInterval(() => { // // axios.get("http://www.projectdivar.com/eventdata/t20?chart=true&force=true"); // }, 20000); CreateDynamicEndpoints(); app.listen(port, () => console.log(`app listening at http://localhost:${port}`) );