const express = require('express') const axios = require('axios') var http = require('http'); var https = require('https'); const fs = require('fs'); const sh = require('secrethash'); var key = fs.readFileSync('./projectdivar.com/privkey.pem'); var cert = fs.readFileSync('./projectdivar.com/cert.pem'); var options = { key: key, cert: cert }; const app = express() var server = https.createServer(options, app); const port = 4505 server.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) const bodyParser = require('body-parser') const { json } = require('body-parser') const moment = require('moment'); const Pool = require('pg').Pool app.use(bodyParser.json()) app.use( bodyParser.urlencoded({ extended: true, }) ) 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); var db = new Pool({ user: 'postgres', password: '', host: 'postgres', database: 'sigcrafting', port: 5432, }) app.get('/', async (req, res) => { res.status(200).send('BUN is love, BUN is life.') }) app.get('/getData', (req, res) => { db.query('select * from crafting_list order by id asc') .then((data) => { if (data.rows.length > 0) { res.status(200).json(data.rows) } else { res.status(204).send("No data") } }) .catch((err) => { res.status(500).send(err.message) }) }) app.get('/getNotifications', (req, res) => { db.query('select * from audit_log where date>$1 order by id asc limit 10', [req.query.date]) .then((data) => { res.status(200).json(data.rows) }) .catch((err) => { console.log(err) res.status(500).send(err.message) }) }) app.get('/lastUpdate', (req, res) => { db.query("select * from site_data limit 1") .then((data) => { res.status(200).json(data.rows) }) .catch((err) => { res.status(500).send(err.message) }) }) app.post("/updateItem", (req, res) => { var itemIcon = "" db.query("update crafting_list set obtained=$1 where id=$2 returning *", [req.body.obtained, req.body.id]) .then((data) => { itemIcon = "https://xivapi.com" + data.rows[0].icon return db.query("update site_data set last_modified=$1", [req.body.last_modified]) }) .then((data) => { return axios.post(process.env.DISCORD_WEBHOOK, { embeds: [{ author: { name: req.body.item_name, icon_url: itemIcon }, color: req.body.obtained == req.body.required ? 32768 : Number(req.body.obtained) > 0 ? 20680 : 2172201, description: "**" + req.body.username + "** " + (req.body.operation === "FINISH" ? " has finished " + (req.body.finalcraft === true ? "crafting " : "collecting ") + req.body.required + " __" + req.body.item_name + "__!" : req.body.operation === "INCREASE" ? " has collected " + req.body.obtained + " / " + req.body.required + " __" + req.body.item_name + "__ (+" + (req.body.obtained - req.body.previous_amt) + ")" : " has set __" + req.body.item_name + "__ to " + req.body.obtained + " / " + req.body.required) + "\n\n[Sig Planner - Sig crafts all the things](http://projectdivar.com:3001)", footer: { "text": "Progress: " + ((req.body.itemCount + req.body.obtained - req.body.previous_amt) / req.body.totalItemCount * 100).toFixed(2) + "% (" + (req.body.itemCount + req.body.obtained - req.body.previous_amt) + "/" + req.body.totalItemCount + ")" } }] }) }) .then((data) => { return db.query("insert into audit_log(username,obtained,required,item_name,operation,date,previous_amt) values($1,$2,$3,$4,$5,$6,$7)", [req.body.username, req.body.obtained, req.body.required, req.body.item_name, req.body.operation === "FINISH" && req.body.finalcraft === true ? "FINISH_CRAFT" : req.body.operation, req.body.last_modified, req.body.previous_amt]) }) .then((data) => { res.status(200).send("Yay!") }) .catch((err) => { console.log(err.message) res.status(500).send(err.message) }) }) app.post("/updateTimer", (req, res) => { if (req.body.timer_start !== null && req.body.timer_end !== null && req.body.timer_start !== undefined && req.body.timer_end !== undefined) { var splitter = req.body.timer_start.split(",") var splitter2 = req.body.timer_end.split(",") for (t of splitter) { if (t == "") continue; if (t.split(":").length != 2 || (t.split(":").length == 2 && (isNaN(t.split(":")[0]) || isNaN(t.split(":")[1])))) { res.status(500).send("Invalid input!") return } } for (t of splitter2) { if (t == "") continue; if (t.split(":").length != 2 || (t.split(":").length == 2 && (isNaN(t.split(":")[0]) || isNaN(t.split(":")[1])))) { console.log(t) res.status(500).send("Invalid input!") return } } db.query("update crafting_list set timer_start=$1,timer_end=$2 where id=$3 returning *", [req.body.timer_start, req.body.timer_end, req.body.id]) .then((data) => { itemIcon = "https://xivapi.com" + data.rows[0].icon return db.query("update site_data set last_modified=$1", [req.body.last_modified]) }) .then((data) => { res.status(200).send("Yay!") }) .catch((err) => { console.log(err.message) res.status(500).send(err.message) }) } else { res.status(500).send("Invalid input!") } }) app.post('/setItem', async (req, res) => { await db.query('select * from crafting_list where itemid=$1', [req.body.itemid]) .then((data) => { if (data.rows.length == 0) { db.query('insert into crafting_list(itemid,name,obtained,required,icon) values($1,$2,$3,$4,$5)', [req.body.itemid, req.body.name, req.body.obtained, req.body.required, req.body.icon]) .then((data) => { res.status(200).send("Yay!") }) .catch((err) => { res.status(500).send(err.message) }) } else { db.query('update crafting_list set required=$1 where itemid=$2', [Number(data.rows[0].required) + Number(req.body.required), req.body.itemid]) .then((data) => { res.status(200).send("Yayay!") }) .catch((err) => { res.status(500).send(err.message) }) } }) }) app.post('/addItem', async (req, res) => { await db.query('select * from crafting_list where itemid=$1', [req.body.itemid]) .then((data) => { if (data.rows.length > 0) { db.query('update crafting_list set obtained=$1 where itemid=$2', [Math.min(data.rows[0].required, Number(data.rows[0].obtained) + Number(req.body.obtained)), req.body.itemid]) .then((data) => { res.status(200).send("AYAYA") }) .catch((err) => { res.status(500).send(err.message) }) } else { res.status(200).send("Nothing to do!") } }) }) function ConvertIssues(str, repoURL) { str += ' ' //Hack to fix issues with not finding a space after links. var split = str.split("Issue #"); for (var i = 0; i < split.length - 1; i++) { split[i] += "[Issue #" var spaceLocation = split[i + 1].indexOf(' '); var spaceSubStr = Number(split[i + 1].substr(0, spaceLocation)); if (Number.isInteger(spaceSubStr)) { split[i + 1] = spaceSubStr + "](" + repoURL + "/issues/" + spaceSubStr + ") " + split[i + 1].substr(spaceLocation + 1) } } var split2 = split.join("").split("Issue#") console.log(split2) for (var i = 0; i < split2.length - 1; i++) { split2[i] += "[Issue#" var spaceLocation = split[i + 1].indexOf(' '); var spaceSubStr = Number(split[i + 1].substr(0, spaceLocation)); if (Number.isInteger(spaceSubStr)) { split[i + 1] = spaceSubStr + "](" + repoURL + "/issues/" + spaceSubStr + ") " + split[i + 1].substr(spaceLocation + 1) } } return split2.join(""); } function ParseMentions(str) { //@quapsel: 155789951571591168 //@sigonasr2: 176012829076226048 return str.replaceAll("@Quapsel", "<@155789951571591168>").replaceAll("@sigonasr2", "<@176012829076226048>") } var embedItems = []; function ParseAttachments(str) { var stage = 0; var backTickRepeatCount = 0; var backTicksEnabled = false; var isImage = false; var accumulatedLink = ""; for (var i = 0; i < str.length; i++) { //If at any time we find ```, we continue until they close. if (str[i] == '`') { backTickRepeatCount++; if (backTickRepeatCount == 3) { backTickRepeatCount = 0; backTicksEnabled = !backTicksEnabled; } continue; } else { backTickRepeatCount = 0; } if (backTicksEnabled) { continue; //As long as we are in back tick mode, we don't care what is posted. } switch (stage) { case 0: { //In stage 0 we look for [. if (str[i] == '[') { if (i > 0 && str[i - 1] == '!') { isImage = true; } else { isImage = false; } stage++; } } break; case 1: { //In stage 1 we look for ]. if (str[i] == ']') { stage++; } } break; case 2: { //In stage 2 the next character should be (. If it's not, reset. if (str[i] == '(') { stage++; } else { stage = 0; } } break; case 3: { //In stage 3, we accumulate what's inside and look for ). if (str[i] == ')') { //We're done! We have a new embed. if (accumulatedLink.startsWith("http")) { if (isImage) { embedItems = [...embedItems, { image: { url: accumulatedLink } }] } else { embedItems = [...embedItems, { url: accumulatedLink }] } } else { if (isImage) { embedItems = [...embedItems, { image: { url: "http://sig.projectdivar.com/" + accumulatedLink } }] } else { embedItems = [...embedItems, { url: "http://sig.projectdivar.com/" + accumulatedLink }] } } accumulatedLink = "" isImage = false; stage = 0; break; } else { accumulatedLink += str[i]; } } break; } } return str } embeds = [] setInterval(() => { if (embeds.length > 0) { axios.post(process.env.GITEA_WEBHOOK, embeds.shift()); } }, 1000); app.post("/postUpdate", (req, res) => { res.status(200).send("Thank you!") embedItems = [] if (req.body.action) { switch (req.body.action) { case "opened": { embedItems = [{ author: { name: "Issue #" + req.body.number + " opened by " + req.body?.issue?.user?.username, icon_url: req.body.issue.user.avatar_url }, color: 11731199, description: "**" + req.body.issue.title + ":**\n\n" + ConvertIssues(ParseAttachments(ParseMentions(req.body.issue.body)), req.body.repository?.html_url) + "\n\n[Link to Issue](" + req.body.issue?.html_url + ")", }, ...embedItems]; embeds.push({ embeds: embedItems }); } break; case "reopened": { embeds.push({ embeds: [{ author: { name: "Issue #" + req.body.number + " re-opened by " + req.body.issue.user.username, icon_url: req.body.issue.user.avatar_url }, color: 13789695, description: "**" + req.body.issue.title + ":**\n\n" + ConvertIssues(req.body.issue.body, req.body.repository?.html_url) + "\n\n[Link to Issue](" + req.body.issue?.html_url + ")", }] }); } break; case "assigned": { var assignedList = "" for (assignee of req.body.issue.assignees) { if (assignedList.length == 0) { assignedList += assignee.username } else { assignedList += "," + assignee.username } } embeds.push({ embeds: [{ author: { name: "Assigned users to Issue #" + req.body.number, icon_url: req.body.issue.user.avatar_url }, color: 50363, description: "Assigned **" + assignedList + "** to Issue **" + req.body.issue.title + "**\n\n[Link to Issue](" + req.body.issue.html_url + ")", }] }); } break; case "label_updated": { var labelsList = "" for (label of req.body.issue.labels) { if (labelsList.length == 0) { labelsList += "- **" + label.name + "**" } else { labelsList += "\n- **" + label.name + "**" } } embeds.push({ embeds: [{ author: { name: "Assigned labels to Issue #" + req.body.number, icon_url: req.body.issue.user.avatar_url }, color: 365568, description: "Labels set for Issue **" + req.body.issue.title + "**:\n" + labelsList + "\n\n[Link to Issue](" + req.body.issue.html_url + ")", }] }) } break; case "created": { embeds.push({ embeds: [{ author: { name: "Comment posted on Issue #" + req.body.issue.number, icon_url: req.body.comment.user.avatar_url }, color: 11731199, description: "__**" + req.body.issue.title + "**__\nCommented by **" + req.body.comment.user.username + ":**\n" + ConvertIssues(ParseAttachments(ParseMentions(req.body.comment.body)), req.body.repository.html_url) + "\n\n[Link to Comment](" + req.body.comment.html_url + ")", }] }) } break; case "edited": { if (req.body.comment) { embedItems = [{ author: { name: "Comment edited on Issue #" + req.body.issue.number, icon_url: req.body.comment.user.avatar_url }, color: 11731199, description: "__**" + req.body.issue.title + "**__\nCommented by **" + req.body.comment.user.username + ":**\n" + ConvertIssues(ParseAttachments(ParseMentions(req.body.comment.body)), req.body.repository.html_url) + "\n\n[Link to Comment](" + req.body.comment.html_url + ")", }, ...embedItems] embeds.push({ embeds: embedItems }) } else { embedItems = [{ author: { name: "Edit on Issue #" + req.body.issue.number, icon_url: req.body.issue.user.avatar_url }, color: 11731199, description: "__**" + req.body.issue.title + "**__\nEdited by **" + req.body.issue.user.username + ":**\n" + ConvertIssues(ParseAttachments(ParseMentions(req.body.issue.body)), req.body.repository.html_url) + "\n\n[Link to Issue](" + req.body.issue.html_url + ")", }, ...embedItems] embeds.push({ embeds: embedItems }); } } break; case "deleted": { embeds.push({ embeds: [{ author: { name: "Comment deleted on Issue #" + req.body.issue.number, icon_url: req.body.comment.user.avatar_url }, color: 11731199, description: "__**" + req.body.issue.title + "**__\nCommented by **" + req.body.comment.user.username + ":**\n~~" + ConvertIssues(req.body.comment.body, req.body.repository.html_url) + "~~\n\n[Link to Comment](" + req.body.comment.html_url + ")", }] }) } break; case "closed": { embeds.push({ embeds: [{ author: { name: "Issue #" + req.body.number + " closed by " + req.body.issue.user.username, icon_url: req.body.issue.user.avatar_url }, color: 9502805, description: "**" + req.body.issue.title + ":**\n\n" + ConvertIssues(req.body.issue.body, req.body.repository.html_url) + "\n\n[Link to Issue](" + req.body.issue.html_url + ")", }] }) } break; default: { console.log("Unknown action " + req.body.action + ". Ignoring.") } } } else if (req.body.ref) { for (var commit of req.body.commits) { var filesAdded = commit.added.length var filesRemoved = commit.removed.length var filesModified = commit.modified.length axios.post(process.env.GITEA_WEBHOOK, { embeds: [{ author: { name: commit.author.username + " has pushed a new commit", icon_url: "http://sig.projectdivar.com/assets/update.png" }, color: 17663, description: "**Commit ID " + commit.id + "**\n\n" + ConvertIssues(commit.message, req.body.repository.html_url) + "\n" + (filesAdded > 0 ? "\n" + filesAdded + " file" + (filesAdded == 1 ? "" : "s") + " added" : "") + (filesRemoved > 0 ? "\n" + filesRemoved + " file" + (filesRemoved == 1 ? "" : "s") + " removed" : "") + (filesModified > 0 ? "\n" + filesModified + " file" + (filesModified == 1 ? "" : "s") + " modified" : "") + "\n\n[Link to Commit](" + commit.url + ")", }] }) } } else { console.log("Unknown webhook incoming. Ignoring.") } /* return axios.post(process.env.DISCORD_WEBHOOK,{embeds:[{ author:{ name:req.body.item_name, icon_url:itemIcon }, color:req.body.obtained==req.body.required?32768:Number(req.body.obtained)>0?20680:2172201, description:"**"+req.body.username+"** "+(req.body.operation==="FINISH"?" has finished "+(req.body.finalcraft===true?"crafting ":"collecting ")+req.body.required+" __"+req.body.item_name+"__!": req.body.operation==="INCREASE"?" has collected "+req.body.obtained+" / "+req.body.required+" __"+req.body.item_name+"__ (+"+(req.body.obtained-req.body.previous_amt)+")" :" has set __"+req.body.item_name+"__ to "+req.body.obtained+" / "+req.body.required)+"\n\n[Sig Planner - Sig crafts all the things](http://projectdivar.com:3001)", footer:{ "text":"Progress: "+((req.body.itemCount+req.body.obtained-req.body.previous_amt)/req.body.totalItemCount*100).toFixed(2)+"% ("+(req.body.itemCount+req.body.obtained-req.body.previous_amt)+"/"+req.body.totalItemCount+")" } }] })*/ }) var runInventoryScan = async () => { for (var item of myInv) { const it = item await db.query('select * from crafting_list where itemid=$1', [item.itemId]) .then(async (data) => { for (var row of data.rows) { if (row.required - row.obtained <= it.quantity) { await db.query('update crafting_list set obtained=$1 where itemid=$2', [row.required, it.itemId]) .catch((err) => { console.log(err.message) }) } else { await db.query('update crafting_list set obtained=$1 where itemid=$2', [row.obtained + it.quantity, it.itemId]) .catch((err) => { console.log(err.message) }) break; } } }) .catch((err) => { console.log(err.message) }) } } app.post("/AiL", function (req, res) { var username = decodeURI(req.body.username) var operation = decodeURI(req.body.operation) var checksum = decodeURI(req.body.checksum) var data = decodeURI(req.body.data) const saveDir = "files/AiLsaves/"; if (username && operation && checksum && data) { if (username.length <= 24 && (operation === "GET_LOAD_FILES" || operation === "GET_FILE" || operation === "SAVE_FILE" || operation === "SAVE_METADATA_FILE") && data.length <= 1000000 && Number(checksum) === username.length * 8 + data.length * 2 + operation.length) { //Checksum: Length of username*8+Length of data*2+Length of operation if (!fs.existsSync(`${saveDir + username}`)) { fs.mkdirSync(`${saveDir + username}`); } switch (operation) { case "GET_LOAD_FILES": { if (fs.existsSync(`${saveDir + username}/metadata.dat`)) { res.status(200).send(fs.readFileSync(`${saveDir + username}/metadata.dat`)); } else { fs.writeFileSync(`${saveDir + username}/metadata.dat`, "\n"); res.status(200).send(fs.readFileSync(`${saveDir + username}/metadata.dat`)); } } break; case "GET_FILE": { if (fs.existsSync(`${saveDir + username}/save.${data.padStart(4, '0')}`)) { res.status(200).send(fs.readFileSync(`${saveDir + username}/save.${data.padStart(4, '0')}`)); } else { res.status(404).send("Not Found!"); } } break; case "SAVE_FILE": { var fileData = data.split("|"); var fileNumb = fileData[0]; var saveFileData = fileData[1]; fs.writeFileSync(`${saveDir + username}/save.${fileNumb.padStart(4, '0')}`, saveFileData); res.status(200).send("Saved!"); } break; case "SAVE_METADATA_FILE": { fs.writeFileSync(`${saveDir + username}/metadata.dat`, data); res.status(200).send("Saved!"); } break; default: { res.status(418).send('I\'m definitely a teapot.'); } } } else { res.status(418).send('I\'m definitely a teapot.'); } } else { res.status(400).send('Invalid input!'); } }); //runInventoryScan() //console.log(process.env.DISCORD_WEBHOOK)