Initial commit
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/node_modules
|
||||||
|
/dist
|
||||||
|
/build
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
/pvfiles/**/**.log
|
||||||
|
/pvfiles/**/**.log.acck
|
||||||
|
|
||||||
|
/output
|
||||||
|
|
||||||
|
/uploads
|
||||||
|
files.db
|
||||||
|
users.db
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# [jtw-storage-site](#jtw-storage-site)
|
||||||
|
express storage site
|
||||||
|
|
||||||
|
### Requirements/Prerequisites
|
||||||
|
- Node.js 18^
|
||||||
|
- Good internet
|
||||||
|
|
||||||
|
Use `reg <user> <password>` to create accounts. This application may not always be safe.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"name": "express-jtw-update-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"bin": "build/src/index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"cli-color": "^2.0.3",
|
||||||
|
"colorts": "^0.1.63",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
|
"dotenv": "^16.4.1",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"express-error-handler": "^1.1.0",
|
||||||
|
"express-session": "^1.18.2",
|
||||||
|
"luxon": "^3.7.2",
|
||||||
|
"moment-timezone": "^0.5.44",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
|
"nodemon": "^3.1.4",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
|
"serve-index": "^1.9.1",
|
||||||
|
"socket.io": "^4.7.4",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"timers-ext": "^0.1.7",
|
||||||
|
"toidentifier": "^1.0.1",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"type": "^1.2.0",
|
||||||
|
"type-is": "^1.6.18",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"undici-types": "^5.26.5",
|
||||||
|
"unpipe": "^1.0.0",
|
||||||
|
"untildify": "^4.0.0",
|
||||||
|
"util-deprecate": "^1.0.2",
|
||||||
|
"utils-merge": "^1.0.1",
|
||||||
|
"vary": "^1.1.2",
|
||||||
|
"which": "^2.0.2",
|
||||||
|
"wrap-ansi": "^8.1.0",
|
||||||
|
"wrappy": "^1.0.2",
|
||||||
|
"xtend": "^4.0.2",
|
||||||
|
"y18n": "^5.0.8",
|
||||||
|
"yargs": "^16.2.0",
|
||||||
|
"yargs-parser": "^20.2.9"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf output/",
|
||||||
|
"d": "ts-node src/index.ts",
|
||||||
|
"build": "npm run clean && tsc && copyfiles .env package.json output/",
|
||||||
|
"quick": "npm run clean && tsc && node ./output/src/index.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/cli-color": "^2.0.6",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/express-session": "^1.18.2",
|
||||||
|
"@types/geoip-lite": "^1.4.4",
|
||||||
|
"@types/ip": "^1.1.3",
|
||||||
|
"@types/luxon": "^3.7.1",
|
||||||
|
"@types/morgan": "^1.9.9",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"@types/ping": "^0.4.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ConsoleCommandData } from "../types/Command"
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'clear',
|
||||||
|
description: "Clears the console.",
|
||||||
|
usage: false,
|
||||||
|
aliases: ["cls"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Main(args: Array<string>): string {
|
||||||
|
console.clear()
|
||||||
|
|
||||||
|
return "Cleared console."
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { ConsoleCommandData } from "../types/Command"
|
||||||
|
import bcrypt from "bcrypt"
|
||||||
|
import { db, db_file } from "../util/database"
|
||||||
|
import fs from "fs"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: "delete",
|
||||||
|
description: "Deletes a user and all their files.",
|
||||||
|
usage: "delete <username>",
|
||||||
|
aliases: [ "del" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Main(args: Array<string>): Promise<any> {
|
||||||
|
if (args.length < 1)
|
||||||
|
return "Missing username. Use /help delete for help with this command."
|
||||||
|
|
||||||
|
const username = args[0]
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
db.get(
|
||||||
|
`SELECT * FROM users WHERE username = ?`,
|
||||||
|
[username],
|
||||||
|
(err, user: any) => {
|
||||||
|
if (err) {
|
||||||
|
return resolve("Database error while looking up user.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return resolve("User not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = user.id
|
||||||
|
|
||||||
|
db_file.all(
|
||||||
|
`SELECT stored_name FROM files WHERE user_id = ?`,
|
||||||
|
[userId],
|
||||||
|
(err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
return resolve("Database error fetching user files.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2a) Remove files from disk
|
||||||
|
rows.forEach((f: any) => {
|
||||||
|
const filePath = path.join("uploads", f.stored_name)
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlink(filePath, (fsErr) => {
|
||||||
|
if (fsErr) {
|
||||||
|
console.error(`Failed to delete file: ${filePath}`, fsErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
db_file.run(
|
||||||
|
`DELETE FROM files WHERE user_id = ?`,
|
||||||
|
[userId],
|
||||||
|
(fileDelErr: any) => {
|
||||||
|
if (fileDelErr) {
|
||||||
|
return resolve("Error deleting file records.")
|
||||||
|
}
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`DELETE FROM users WHERE username = ?`,
|
||||||
|
[username],
|
||||||
|
(userDelErr: any) => {
|
||||||
|
if (userDelErr) {
|
||||||
|
return resolve("Error deleting user.")
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(`User '${username}' deleted successfully.`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ConsoleCommandData } from "../types/Command";
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: "eval",
|
||||||
|
description: "Evaluates JavaScript code.",
|
||||||
|
usage: `eval ${'<code>'['bold']}`,
|
||||||
|
aliases: ["do"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Main(args: string[]) {
|
||||||
|
let result = ""
|
||||||
|
result = args.join(" ")
|
||||||
|
|
||||||
|
const showRawOutput = result.includes("--raw") || result.includes("-r")
|
||||||
|
result = result.replace('--raw', '').replace('-r', '')
|
||||||
|
let evaled = eval(result)
|
||||||
|
|
||||||
|
if (!showRawOutput) {
|
||||||
|
result = result.replaceAll(process.env.token, "\"[token\"").replaceAll('client.token', '\"[token]\"').replaceAll('process.env.token', '\"[token]\"');
|
||||||
|
|
||||||
|
//Convert evaled into a JSON String if it is an Object
|
||||||
|
if (typeof evaled === "object") evaled = JSON.stringify(evaled);
|
||||||
|
|
||||||
|
//Do this if evaled is anything else other than a number, text, or true/false
|
||||||
|
if (typeof evaled !== "number" && typeof evaled !== "string" && typeof evaled !== "boolean") evaled = `Output could not be converted to text (output was of type: ${typeof evaled}).`;
|
||||||
|
evaled = evaled.toString().replaceAll(process.env.token.toString(), "[token]")
|
||||||
|
result = result.toString().replaceAll(process.env.token, "[token]").replaceAll('client.token', '[token]').replaceAll('process.env.token', '[token]');
|
||||||
|
}
|
||||||
|
return `${showRawOutput ? "Raw e" : "E"}valuation result:\n${evaled}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { ConsoleCommandData } from "../types/Command"
|
||||||
|
import treekill from 'tree-kill'
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
const c = new Logger("CLIENT/exit.js")
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'exit',
|
||||||
|
description: "Stops the application.",
|
||||||
|
usage: false,
|
||||||
|
aliases: ["stop", "quit"]
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitedAlready: boolean = false;
|
||||||
|
|
||||||
|
export function Main(args: Array<string>): string | number {
|
||||||
|
var code: number = Number.parseInt(args[0]) || 1
|
||||||
|
|
||||||
|
HandleExit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HandleExit() {
|
||||||
|
['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'].forEach((eventType) => {
|
||||||
|
process.on(eventType, ()=>{})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
if (!exitedAlready) {
|
||||||
|
let RootProcPid = process.ppid
|
||||||
|
exitedAlready = true;
|
||||||
|
c.log("Have a nice day!")
|
||||||
|
treekill(RootProcPid)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
treekill(process.ppid)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import Logger from "../util/Logger";
|
||||||
|
const cm = new Logger("CLIENT/help.js")
|
||||||
|
import fs from 'fs'
|
||||||
|
import 'colorts/lib/string'
|
||||||
|
import { ConsoleCommandData } from "../types/Command";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
name: "help",
|
||||||
|
description: "Returns a list of usable commands / usage of a command.",
|
||||||
|
usage: `help ${"[command name]"["italic"]}`,
|
||||||
|
aliases: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Main(args: Array<string>) {
|
||||||
|
const cmdname = args[0]
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (cmdname) {
|
||||||
|
const metadata: ConsoleCommandData = require(cwd + `/output/src/cmd/${cmdname.replace('ts', 'js')}`).metadata
|
||||||
|
|
||||||
|
cm.log('Command name: ' + cmdname)
|
||||||
|
cm.log(`Description: ${metadata.description}`)
|
||||||
|
cm.log(`Usage: ${metadata.usage ? metadata.usage : 'No particular arguments.'}`)
|
||||||
|
if (Array.isArray(metadata.aliases) && metadata.aliases.length)
|
||||||
|
cm.log(`Aliases: ${metadata.aliases.join(', ')}`)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
cm.log(`The command ${cmdname} was not found.`)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmdname)
|
||||||
|
try {
|
||||||
|
//var commands = "\n"
|
||||||
|
const commandList = fs.readdirSync(cwd + '/output/src/cmd')
|
||||||
|
//var processed_commands: number = 0
|
||||||
|
cm.log(`${"Italic text"["italic"]} represent optional arguments, while ${"bold"["bold"]} text represent mandatory arguments.`)
|
||||||
|
cm.log(`Command usage: ${this.metadata.usage}\n`)
|
||||||
|
cm.log("The following commands are available:")
|
||||||
|
|
||||||
|
for (let i = 0; i < commandList.length; i++) {
|
||||||
|
const commandFile = commandList[i];
|
||||||
|
if (commandFile.includes('.map'))
|
||||||
|
""
|
||||||
|
else {
|
||||||
|
//processed_commands++
|
||||||
|
const commandMetadata = require(cwd + `/output/src/cmd/${commandFile.replace('ts', 'js')}`).metadata
|
||||||
|
const usage = commandMetadata.usage ? commandMetadata.usage : commandFile
|
||||||
|
cm.log(`${commandFile.replace('.js', '')}\t${commandMetadata.description}`)
|
||||||
|
//cm.log(`description: ${commandMetadata.description}`)
|
||||||
|
//cm.log(`usage: ${usage}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
} catch (err) {
|
||||||
|
cm.error("An error occured while running this command: " + err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ConsoleCommandData } from '../types/Command';
|
||||||
|
import { parseInt } from '../util';
|
||||||
|
import GenerateID from '../util/idGen';
|
||||||
|
import { Clear } from '../util/Prompt'
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'idGen',
|
||||||
|
description: "Generates an ID.",
|
||||||
|
usage: "idGen <bit length> [amount]",
|
||||||
|
aliases: ["idgen"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Main(args: Array<string>): string {
|
||||||
|
if (!args[0])
|
||||||
|
return "Insufficient arguments.";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let bs = parseInt(args[0])
|
||||||
|
let quantity = 1
|
||||||
|
var generated = 0;
|
||||||
|
var ids = []
|
||||||
|
if (args[1]) {
|
||||||
|
quantity = Number.parseInt(args[1])
|
||||||
|
}
|
||||||
|
// the for loop didnt work (??)
|
||||||
|
while (generated < quantity) {
|
||||||
|
generated++
|
||||||
|
ids.push(`${generated}) ${GenerateID(bs)}`)
|
||||||
|
}
|
||||||
|
return `Generated ID(s): \n${ids.join('\n')}`
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.includes('is not an integer'))
|
||||||
|
return "Invalid arguments. Command usage:\nidGen <bytesize> [amount]"
|
||||||
|
else
|
||||||
|
return `An error occured while running the command: ${err}`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { ConsoleCommandData } from "../types/Command"
|
||||||
|
import bcrypt from 'bcrypt'
|
||||||
|
import { db } from "../util/database";
|
||||||
|
import Logger from "../util/Logger";
|
||||||
|
const cm = new Logger("CLIENT/register.js")
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'register',
|
||||||
|
description: "Creates an account.",
|
||||||
|
usage: "register <username> <password>",
|
||||||
|
aliases: ["reg"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Main(args: Array<string>): Promise<any> {
|
||||||
|
if (args.length < 2)
|
||||||
|
return "Missing arguments. Run /help register for help with this command.";
|
||||||
|
|
||||||
|
const username = args[0]
|
||||||
|
const password = args[1]
|
||||||
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
|
db.get(
|
||||||
|
`SELECT username FROM users WHERE username = ?`,
|
||||||
|
[username],
|
||||||
|
(err: any, row: any) => {
|
||||||
|
var output = ""
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return "Database error!"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
output = `The username ${username} is already taken!`;
|
||||||
|
cm.log(output)
|
||||||
|
} else {
|
||||||
|
db.run(
|
||||||
|
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
|
||||||
|
[username, passwordHash],
|
||||||
|
(err: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
output = "Error creating account!";
|
||||||
|
} else {
|
||||||
|
output = "Account created.";
|
||||||
|
}
|
||||||
|
cm.log(output)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import proc, { exec, execSync } from 'child_process'
|
||||||
|
import { ConsoleCommandData } from '../types/Command';
|
||||||
|
import { server } from '..';
|
||||||
|
import { db } from '../util/database';
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'restart',
|
||||||
|
description: "Restarts the application.",
|
||||||
|
usage: false,
|
||||||
|
aliases: ["re", "reboot"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function run(command: string) {
|
||||||
|
try {
|
||||||
|
execSync(command, { stdio: 'inherit' })
|
||||||
|
} catch (err) {
|
||||||
|
//c.error(`Caught exception while rebuilding the project. E:\n${err}`)
|
||||||
|
process.exit(0) // probably a sigint?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Main(args: Array<string>): string {
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
db.close();
|
||||||
|
} catch { } // if this fails then the server and db are probably closed already
|
||||||
|
|
||||||
|
run("npm run quick");
|
||||||
|
|
||||||
|
return "The new process has crashed. Run \"restart\" to rebuild the application again."
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ConsoleCommandData } from '../types/Command';
|
||||||
|
import { Clear } from '../util/Prompt'
|
||||||
|
|
||||||
|
export const metadata: ConsoleCommandData = {
|
||||||
|
name: 'say',
|
||||||
|
description: "Repeats the input.",
|
||||||
|
usage: "say <args...>",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// test cmd (why)
|
||||||
|
export function Main(args: Array<string>): string {
|
||||||
|
if (!args)
|
||||||
|
return Clear;
|
||||||
|
|
||||||
|
return args.join(' ')
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
var pages: { name: string, addr: string }[] = [
|
||||||
|
{ name: 'staticfiles', addr: '/staticfiles' },
|
||||||
|
{ name: 'upload', addr: '/upload'},
|
||||||
|
{ name: 'posts', addr: '/posts' },
|
||||||
|
{ name: "home", addr: '/' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/*
|
||||||
|
<div class="extra">
|
||||||
|
Pages<br>
|
||||||
|
) <a href="/staticfiles">/staticfiles</a><br>
|
||||||
|
) <a href="/">Home</a>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pages_string = (activepage: string) => {
|
||||||
|
var npages = [...pages];
|
||||||
|
var base = `
|
||||||
|
<div class="extra">
|
||||||
|
Pages<br>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
if (activepage == "/register")
|
||||||
|
npages.push({ name: "login", addr: "/login" })
|
||||||
|
else if (activepage == "/login")
|
||||||
|
npages.push({ name: "register", addr: "/register" })
|
||||||
|
|
||||||
|
for (var i = 0; i < npages.length; i++) {
|
||||||
|
const page = npages[i];
|
||||||
|
if (activepage !== page.addr)
|
||||||
|
base += `) <a href="${page.addr}">${page.name}</a><br>
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
npages = pages; // reset the values because if u cycle through register and login itll repeat itself ;-;
|
||||||
|
|
||||||
|
base += `</div>`
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pages_string;
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
const styles_string = (add: string = null, override: string = null) => {
|
||||||
|
var out = `<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 50
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-bottom: 10%;
|
||||||
|
text-align: left;
|
||||||
|
color: black;
|
||||||
|
font-family: Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #3c72a3
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #3c72a3
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #383737;
|
||||||
|
color: #b0acac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tonly {
|
||||||
|
color: #b0acac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tonly {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra {
|
||||||
|
font-size: 24px
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-box {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 30%;
|
||||||
|
padding: 6px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form {
|
||||||
|
max-width: 60%;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.03);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form input[type="text"],
|
||||||
|
.post-form textarea,
|
||||||
|
.post-form select {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form input {
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 18px
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form button {
|
||||||
|
margin-top: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #3c72a3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poststylebtn {
|
||||||
|
margin-top: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #3c72a3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form button:hover {
|
||||||
|
background-color: #2f5c86;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
.post-form {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form input,
|
||||||
|
.post-form textarea,
|
||||||
|
.post-form select {
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
color: #b0acac;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form button {
|
||||||
|
background-color: #4a88c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form button:hover {
|
||||||
|
background-color: #3a6d9b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tonly {
|
||||||
|
font-family: inherit;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tonly-formf::file-selector-button {
|
||||||
|
font-family: inherit;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
color: inherit;
|
||||||
|
margin-right: 5%;
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tonly-formf {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${add ?? add}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
|
|
||||||
|
if (override)
|
||||||
|
out = override;
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styles_string;
|
||||||
|
|
||||||
|
export const parse_date_string = `
|
||||||
|
function parseDate(input, useRtf = true, showTime = true, timeZone) {
|
||||||
|
|
||||||
|
const date = new Date(input);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const tz = timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
const diffSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
if (useRtf) {
|
||||||
|
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||||
|
|
||||||
|
if (diffSeconds < 60) return rtf.format(-diffSeconds, "second");
|
||||||
|
if (diffSeconds < 3600) return rtf.format(-Math.floor(diffSeconds / 60), "minute");
|
||||||
|
if (diffSeconds < 86400) return rtf.format(-Math.floor(diffSeconds / 3600), "hour");
|
||||||
|
if (diffSeconds < 604800) return rtf.format(-Math.floor(diffSeconds / 86400), "day");
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toLocaleString("en-US", {
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
...(showTime && {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true,
|
||||||
|
timeZoneName: "short"
|
||||||
|
}),
|
||||||
|
timeZone: tz
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseDateC(date) {
|
||||||
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
const formatted = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone,
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true
|
||||||
|
}).format(date);
|
||||||
|
|
||||||
|
const result = formatted.replace(",", "")
|
||||||
|
.replace(/(\d{4})/, "$1 at");
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
${/*function toIso(input) {
|
||||||
|
const date = input instanceof Date ? input : new Date(input);
|
||||||
|
|
||||||
|
if (isNaN(date)) {
|
||||||
|
throw new Error("Invalid date");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pad = n => String(n).padStart(2, "0");
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = pad(date.getMonth() + 1);
|
||||||
|
const day = pad(date.getDate());
|
||||||
|
const hour = pad(date.getHours());
|
||||||
|
const minute = pad(date.getMinutes());
|
||||||
|
const second = pad(date.getSeconds());
|
||||||
|
|
||||||
|
const offsetMinutes = -date.getTimezoneOffset();
|
||||||
|
const sign = offsetMinutes >= 0 ? "+" : "-";
|
||||||
|
const abs = Math.abs(offsetMinutes);
|
||||||
|
const offsetHour = pad(Math.floor(abs / 60));
|
||||||
|
const offsetMinute = pad(abs % 60);
|
||||||
|
|
||||||
|
return \`\${year}-\${month}-\${day}T\${hour}:\${minute}:\${second}\${sign}\${offsetHour}:\${offsetMinute}\`
|
||||||
|
}*/""}
|
||||||
|
|
||||||
|
function toIso(input) {
|
||||||
|
const date = new Date(input);
|
||||||
|
if (isNaN(date)) throw new Error("Invalid date");
|
||||||
|
|
||||||
|
const pad = n => String(n).padStart(2, "0");
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = pad(date.getMonth() + 1);
|
||||||
|
const day = pad(date.getDate());
|
||||||
|
const hour = pad(date.getHours());
|
||||||
|
const minute = pad(date.getMinutes());
|
||||||
|
const second = pad(date.getSeconds());
|
||||||
|
|
||||||
|
const offsetMinutes = -date.getTimezoneOffset(); // offset in minutes
|
||||||
|
const sign = offsetMinutes >= 0 ? "+" : "-";
|
||||||
|
const absOffset = Math.abs(offsetMinutes);
|
||||||
|
const offsetHour = pad(Math.floor(absOffset / 60));
|
||||||
|
const offsetMinute = pad(absOffset % 60);
|
||||||
|
|
||||||
|
return \`\${year}\-\${month}-\${day}T\${hour}:\${minute}:\${second}\${sign}\${offsetHour}:\${offsetMinute}\`;
|
||||||
|
}
|
||||||
|
`
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+186
@@ -0,0 +1,186 @@
|
|||||||
|
const dev = false
|
||||||
|
|
||||||
|
import express, { Express } from "express";
|
||||||
|
const serveIndex = require('serve-index');
|
||||||
|
import { Prompt } from "./util/Prompt";
|
||||||
|
import multer from 'multer'
|
||||||
|
import Logger from "./util/Logger";
|
||||||
|
const s = new Logger("Server", "blue")
|
||||||
|
import morgan from 'morgan'
|
||||||
|
import fs from 'fs'
|
||||||
|
import SetRndPasswd from "./util/SetPasswd";
|
||||||
|
import path from "path";
|
||||||
|
import * as util from './util'
|
||||||
|
var missingDirectories = true;
|
||||||
|
|
||||||
|
if (util.mkDirsManIfNotExists([
|
||||||
|
path.join(process.cwd(), "pvfiles"),
|
||||||
|
path.join(process.cwd(), "uploads"),
|
||||||
|
path.join(process.cwd(), "staticfiles")
|
||||||
|
]))
|
||||||
|
missingDirectories = false;
|
||||||
|
else {
|
||||||
|
s.log("Missing directories. Please ensure the folders 'pvfiles', 'uploads', and 'staticfiles' exist in the application's root folder.")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), ".env"))) {
|
||||||
|
const secret = PasswordGen(24)
|
||||||
|
|
||||||
|
const rString = `
|
||||||
|
port=80
|
||||||
|
altregion=us # the displayed region in each page's header
|
||||||
|
|
||||||
|
servicename=File Server # the displayed service name in each page's header
|
||||||
|
description=A simple file server built with Express. # the displayed description in the home page
|
||||||
|
|
||||||
|
bootmessage=Welcome!
|
||||||
|
|
||||||
|
session_secret=${secret} # the session secret used for logins. you can generate a new secret using the idGen command.
|
||||||
|
enable_register=false # it's advised to keep this off unless you're hosting a public instance
|
||||||
|
|
||||||
|
postwrite_notice=Hi! # the notice on top of the post form
|
||||||
|
postwrite_html=false # if enabled, users can use html in posts. this is unsafe! you can set it to true if you trust your users.
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(process.cwd(), ".env"), rString);
|
||||||
|
|
||||||
|
s.log("A .env file has been created in the root directory. If you'd like, you can edit its values.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorHandler = require('express-error-handler'),
|
||||||
|
handler = errorHandler({
|
||||||
|
static: {
|
||||||
|
'404': "/404"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.clear()
|
||||||
|
|
||||||
|
s.log('Preparing the application...')
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export const app: Express = express();
|
||||||
|
|
||||||
|
export const port = process.env.port || 3000;
|
||||||
|
|
||||||
|
var storage: multer.StorageEngine
|
||||||
|
export var upload: multer.Multer;
|
||||||
|
|
||||||
|
export var server: Server;
|
||||||
|
|
||||||
|
import moment from 'moment-timezone'
|
||||||
|
import { Server } from "http";
|
||||||
|
import session from "express-session";
|
||||||
|
import PasswordGen from "./util/passwordGen";
|
||||||
|
|
||||||
|
var accessLogStream = fs.createWriteStream('./pvfiles/interactions.log', { flags: 'a' });
|
||||||
|
|
||||||
|
app.enable("trust proxy");
|
||||||
|
try {
|
||||||
|
|
||||||
|
// upload handler
|
||||||
|
storage = multer.diskStorage({
|
||||||
|
destination: (req, file, cb) =>
|
||||||
|
cb(null, "uploads"),
|
||||||
|
filename: (req, file, cb) =>
|
||||||
|
cb(null, `${Date.now()}_${file.originalname}`)
|
||||||
|
})
|
||||||
|
upload = multer({storage})
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
session({
|
||||||
|
name: "session",
|
||||||
|
secret: process.env.session_secret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
cookie: {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "lax",
|
||||||
|
secure: dev
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
//app.use("/uploads", express.static("uploads"));
|
||||||
|
|
||||||
|
app.use('/staticfiles', express.static('staticfiles'), serveIndex('staticfiles', { 'icons': true }))
|
||||||
|
server = app.listen(port, () => {
|
||||||
|
s.log(`Server started at 127.0.0.1:${port}`)
|
||||||
|
s.log(`${process.env.bootmessage || "Ready!"}`)
|
||||||
|
Prompt()
|
||||||
|
}).on('error', (e: any) => {
|
||||||
|
if (e.code === "EADDRINUSE") {
|
||||||
|
console.log('\n\n')
|
||||||
|
s.log(`Address already in use. Your port (${port}) might be in use. Use another port or make it available.`)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Setup listeners
|
||||||
|
app.get('/api/ping/assist', (req, res, next) => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
res.json({ timestamp });
|
||||||
|
});
|
||||||
|
|
||||||
|
// SET LOG PASSWORDS
|
||||||
|
//SetRndPasswd('../pvfiles/app.log')
|
||||||
|
s.log(`The AccessKey for interactions.log is ${SetRndPasswd('./pvfiles/interactions.log')}`)
|
||||||
|
|
||||||
|
function removeURLParameter(url: string | undefined, parameterName: string) {
|
||||||
|
//prefer to use l.search if you have a location/link object
|
||||||
|
var urlparts: any = url?.split('?');
|
||||||
|
if (urlparts.length >= 2) {
|
||||||
|
|
||||||
|
var prefix = encodeURIComponent(parameterName) + '=';
|
||||||
|
var pars = urlparts[1].split(/[&;]/g);
|
||||||
|
|
||||||
|
//reverse iteration as may be destructive
|
||||||
|
for (var i = pars.length; i-- > 0;) {
|
||||||
|
//idiom for string.startsWith
|
||||||
|
if (pars[i].lastIndexOf(prefix, 0) !== -1) {
|
||||||
|
pars.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : '');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
morgan.token('path', (req, res) => {
|
||||||
|
return removeURLParameter(req?.url, "password")
|
||||||
|
})
|
||||||
|
|
||||||
|
morgan.token('datenow', (req, res) => {
|
||||||
|
return `${moment().format("MM/DD/YYYY h:mm:ss A")} ${Intl.DateTimeFormat().resolvedOptions().timeZone || ""}`;
|
||||||
|
})
|
||||||
|
app.use(morgan(
|
||||||
|
':datenow - :method by :remote-addr in :path :status :response-time ms - content length :res[content-length] bytes',
|
||||||
|
{
|
||||||
|
stream: accessLogStream
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// set up listeners
|
||||||
|
const listeners = fs.readdirSync('./src/listeners')
|
||||||
|
for (let count = 0; count < listeners.length; count++) {
|
||||||
|
const listenerName = listeners[count];
|
||||||
|
|
||||||
|
require(`${process.cwd()}/output/src/listeners/${listenerName.replaceAll("ts", "js")}`).Api(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up pages
|
||||||
|
const pages = fs.readdirSync('./src/pages')
|
||||||
|
for (let count = 0; count < pages.length; count++) {
|
||||||
|
const pageFilename = pages[count];
|
||||||
|
|
||||||
|
if (pageFilename.endsWith('.ts'))
|
||||||
|
require(`${process.cwd()}/output/src/pages/${pageFilename.replaceAll(".ts", ".js")}`).Main(app)
|
||||||
|
// in case a base html file was there
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(handler)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from "../util/Logger";
|
||||||
|
export var latency: number = 0;
|
||||||
|
var c = new Logger("Server(Api)", "gray")
|
||||||
|
var ce = new Logger("Api(Send)", "italic")
|
||||||
|
|
||||||
|
|
||||||
|
export function Api(app: Express) {
|
||||||
|
c.log('GetApi loaded.')
|
||||||
|
|
||||||
|
app.get('/api/:apiID', (req: Request, res: Response) => {
|
||||||
|
switch (req.params.apiID) {
|
||||||
|
case "logout":
|
||||||
|
req.session.destroy((err: any) => {
|
||||||
|
if (err) {
|
||||||
|
c.error(`User failed to log out: ${err}`,)
|
||||||
|
res.status(500).send("Failed to log out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.clearCookie("session")
|
||||||
|
res.redirect("/login")
|
||||||
|
})
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*case "ping":
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
fetch(`${process.env.https}://${ipAddress}:${require('..').port}/api/ping/assist`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const endTime = Date.now();
|
||||||
|
latency = endTime - startTime;
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
res.send(`${latency}`)
|
||||||
|
|
||||||
|
break;*/
|
||||||
|
|
||||||
|
/*case "send":
|
||||||
|
const message: any = req.query.msg || undefined
|
||||||
|
const sender: any = req.query.sender || "API call"
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
ce.log(`${sender}: ${message}`)
|
||||||
|
res.status(200).send("Sent message as a string to internal logs.<br>Message content: " + message + "<br>Sender: " + sender)
|
||||||
|
} else {
|
||||||
|
res.status(500).send("No message provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
break;*/ // wtf
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.status(404).send(`An API named ${req.params.apiID} could not be found.`)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from "../util/Logger";
|
||||||
|
import fs from "fs";
|
||||||
|
import SetRndPasswd from "../util/SetPasswd";
|
||||||
|
export var latency: number = 0;
|
||||||
|
var c = new Logger("Server(Pvf)", "gray")
|
||||||
|
var ce = new Logger("Server(PvfSetup)", "gray")
|
||||||
|
export function findInArray(array: any[], find: any) {
|
||||||
|
for (const item of array) {
|
||||||
|
if (item === find)
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
import styles_string from "../components/styles";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
import { bytesToSize } from "../util";
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
export function Api(app: Express) {
|
||||||
|
|
||||||
|
var files: any[] = []
|
||||||
|
|
||||||
|
/*if (process.platform === "win32")
|
||||||
|
var paths: string[] = [
|
||||||
|
`${__dirname.replace('\\src\\listeners', '')}\\..\\pvfiles\\:filename`,
|
||||||
|
]
|
||||||
|
else*/
|
||||||
|
var paths: string[] = [
|
||||||
|
`${process.cwd()}/pvfiles/:filename`,
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const watchPath of paths) {
|
||||||
|
var cleanPath = watchPath.replace(':filename', '');
|
||||||
|
|
||||||
|
ce.log(`Checking contents of ${cleanPath}`)
|
||||||
|
|
||||||
|
for (const filename of fs.readdirSync(`${cleanPath}`)) {
|
||||||
|
files.push(
|
||||||
|
`${filename.toString()}`,
|
||||||
|
);
|
||||||
|
ce.log(`Found a PV file: ${filename}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALIDATE
|
||||||
|
for (const filename of files) {
|
||||||
|
if (!filename.endsWith(".acck") && !fs.existsSync(`./pvfiles/${filename}.acck`)) {
|
||||||
|
ce.log(`File ${filename} has no access key file! Creating one now!`)
|
||||||
|
SetRndPasswd(`./pvfiles/${filename}`)
|
||||||
|
ce.log(`An access key has been set for ${filename} ; use getPvf ${filename} to get the new access key.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.get('/pvfiles/:filename', (req: Request, res: Response) => {
|
||||||
|
var filename: string = req.params.filename;
|
||||||
|
var password = req.query.AccessKey
|
||||||
|
var direct = req.query.direct === "true" ? true : false || false;
|
||||||
|
var dl = req.query.dl === "true" ? true : false || false;
|
||||||
|
var fullPath: string = `${cleanPath}${findInArray(files, filename)}`;
|
||||||
|
|
||||||
|
if (files.includes(filename) && fs.existsSync(fullPath)) {
|
||||||
|
if (files.includes(filename + '.acck') && fs.existsSync(`${fullPath}.acck`)) {
|
||||||
|
if (password === fs.readFileSync(`${fullPath}.acck`, { encoding: 'utf8' })) {
|
||||||
|
var here_direct = `/pvfiles/${filename}?AccessKey=${password}&direct=true`
|
||||||
|
var here_dl = `/pvfiles/${filename}?AccessKey=${password}&dl=true`;
|
||||||
|
|
||||||
|
if (direct) {
|
||||||
|
c.log(`A private file was sent to ${req.ip}: ${fullPath}`)
|
||||||
|
res.status(200).sendFile(fullPath)
|
||||||
|
} else if (dl) {
|
||||||
|
c.log(`A private file was downloaded by ${req.ip}: ${fullPath}`)
|
||||||
|
res.status(200).download(fullPath)
|
||||||
|
if (fullPath.endsWith(".pdf"))
|
||||||
|
res.status(200).sendFile(fullPath) // show the pdf since most browsers just show it when given a link
|
||||||
|
} else {
|
||||||
|
const stat = fs.statSync(fullPath)
|
||||||
|
const size = bytesToSize(stat.size)
|
||||||
|
|
||||||
|
res.status(200).send(
|
||||||
|
`<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>${filename}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${styles_string()}
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
<div class="details">
|
||||||
|
${/*<p class="details">Local version: ${require('../updateInfo').GameVersion}</p>*/""}
|
||||||
|
<p>Server region: ${'(alternate) ' + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span style="font-size: 24px">${filename}</span> ${size}<br>
|
||||||
|
<br>
|
||||||
|
<span style="font-size: 18px;">
|
||||||
|
<a href='${here_direct}'><button class="tonly">Open file</button></a>
|
||||||
|
|
||||||
|
<a href='${here_dl}'><button class="tonly">Download file</button></a>
|
||||||
|
</span>
|
||||||
|
<br><br>
|
||||||
|
${pages_string("/pvfiles")}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (!password) {
|
||||||
|
res.status(500).send(`Cannot access ${filename} without an AccessKey.`)
|
||||||
|
} else
|
||||||
|
res.status(500).send('AccessKey entered is incorrect.')
|
||||||
|
} else
|
||||||
|
res.status(404).send("File not found.")
|
||||||
|
} else
|
||||||
|
res.status(404).send(
|
||||||
|
`File not found.`)
|
||||||
|
|
||||||
|
/* Searched: ${fullPath}
|
||||||
|
<br>Input filename: ${filename}
|
||||||
|
<br>File array: ${files.toString()}
|
||||||
|
<br>Input filename matches file: ${fs.existsSync.log(fullPath)} | but matches an entry in the files array? ${files.includes(filename)}*/
|
||||||
|
})
|
||||||
|
c.log(`GetPrivateFile loaded.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
var c = new Logger("Server(Pages)", "gray")
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "404".')
|
||||||
|
|
||||||
|
app.get('/404', async (req: Request, res: Response) => {
|
||||||
|
res.status(404).send(
|
||||||
|
/*`
|
||||||
|
|
||||||
|
Update server for Journey to Nowhere. Local version: ${require('../gameinfo').GameVersion}
|
||||||
|
<br>Server region: ${ipd?.country || '(alternate) ' + process.env.altregion}
|
||||||
|
<br>Server ping: ${latency}ms
|
||||||
|
|
||||||
|
`*/
|
||||||
|
`<!DOCTYPE html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 50
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-bottom: 10%;
|
||||||
|
text-align: left;
|
||||||
|
color: black;
|
||||||
|
font-family: Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #3c72a3
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #2a567d
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #383737;
|
||||||
|
color: #b0acac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra {
|
||||||
|
font-size: 24px
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
<div class="details">
|
||||||
|
${/*<p class="details">Local version: ${require('../updateInfo').GameVersion}</p>*/""}
|
||||||
|
<p>Server region: ${'(alternate) ' + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
<br><hr><br>
|
||||||
|
<div class="description">
|
||||||
|
${process.env.notfound || "The file you were looking for does not exist on the server."}
|
||||||
|
</div>
|
||||||
|
</body>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from "../util/Logger";
|
||||||
|
import "../updateInfo";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import { db } from "../util/database";
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { User } from "../types/User";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
import styles_string from "../components/styles";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const c = new Logger("Server(Pages)", "gray");
|
||||||
|
|
||||||
|
const login_string = (alert: string = "<br>", active_page: string = "/login"): string => {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${process.env.servicename || "File Server"} | Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${styles_string()}
|
||||||
|
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<p>Server region: ${"(alternate) " + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr><br>
|
||||||
|
|
||||||
|
<span class="description">
|
||||||
|
Login
|
||||||
|
</span>
|
||||||
|
|
||||||
|
${`<p id="alerts">${alert}</p>` ? alert : ""}
|
||||||
|
|
||||||
|
<form class="post-form" method="POST" action="/login">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" name="username" required><br>
|
||||||
|
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" name="password" required><br>
|
||||||
|
|
||||||
|
<button type="submit">Log in</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
${pages_string(active_page)}
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
}
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "login".');
|
||||||
|
|
||||||
|
app.use(require("express").urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
app.get("/login", async (req: Request, res: Response) => {
|
||||||
|
if (req.session.user)
|
||||||
|
res.redirect("/")
|
||||||
|
else
|
||||||
|
res.send(login_string());
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/login", async (req: Request, res: Response) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).send(login_string("Missing username or password!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
db.get(
|
||||||
|
`SELECT * FROM users WHERE username = ?`,
|
||||||
|
[username],
|
||||||
|
async (err, user: User) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).send(login_string("Database error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).send(login_string("Invalid username or password."));
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await bcrypt.compare(password, user.password_hash);
|
||||||
|
if (!valid) {
|
||||||
|
return res.status(401).send(login_string("Invalid username or password."));
|
||||||
|
}
|
||||||
|
|
||||||
|
req.session.user = {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username
|
||||||
|
};
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
var c = new Logger("Server(Pages)", "gray")
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import * as dbs from '../util/database'
|
||||||
|
import { User, UserSession } from "../types/User";
|
||||||
|
import { PostRow } from "../types/PostRow";
|
||||||
|
import GenerateID from "../util/idGen";
|
||||||
|
import { convertToIso, parseDate } from "../util/dateParse";
|
||||||
|
import styles_string, { parse_date_string } from "../components/styles";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
import { escapeHtml } from "../util";
|
||||||
|
const db = dbs.db;
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
|
||||||
|
const heads = (active_page: string, title: string, content: string, extraoverride: string = null, notitleset = false): string => {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
${notitleset ? "" : `<title>${process.env.servicename || "File Server"} | ${title}</title>`}
|
||||||
|
<body>
|
||||||
|
${styles_string(`
|
||||||
|
.postlist_r {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postlist {
|
||||||
|
flex: 1 1 250px;
|
||||||
|
background-color: rgba(0,0,0,0.03);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectarr {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectarr select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c_col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`)}
|
||||||
|
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<p>Server region: ${"(alternate) " + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr><br>
|
||||||
|
|
||||||
|
${title ? `<div class="description">
|
||||||
|
${title}
|
||||||
|
</div>` : ""}
|
||||||
|
|
||||||
|
${content}
|
||||||
|
|
||||||
|
${pages_string(active_page)}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const newpost_string = (alert: string = null) => heads("/posts/new", "New Post", `
|
||||||
|
<form id="changeable_parent" class="post-form" action="/posts" method="POST">
|
||||||
|
<label>Post title:</label>
|
||||||
|
<input id="post_changeable" name="title" placeholder="New Post" required><br>
|
||||||
|
<div class="selectarr">
|
||||||
|
<div class="c_col">
|
||||||
|
<label>Visibility:</label>
|
||||||
|
<select name="visibility">
|
||||||
|
<option value="public">Public</option>
|
||||||
|
<option value="unlisted">Unlisted</option>
|
||||||
|
<option value="private">Private</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="c_col">
|
||||||
|
<label>Font:</label>
|
||||||
|
<select name="font" id="font_sel">
|
||||||
|
<option value="Verdana">Default</option>
|
||||||
|
<option value="monospace">Mono</option>
|
||||||
|
<option value="Bookman Old Style">Serif</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label>Post content:</label>
|
||||||
|
<textarea id="post_changeable" name="content" placeholder="..." rows="15" cols="75" required></textarea><br>
|
||||||
|
<button type="submit" class="tonly">Post</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const fontSelect = document.getElementById("font_sel");
|
||||||
|
const postContent = document.getElementById("changeable_parent");
|
||||||
|
|
||||||
|
fontSelect.addEventListener("change", () => {
|
||||||
|
postContent.style.fontFamily = fontSelect.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
postContent.style.fontFamily = fontSelect.value;
|
||||||
|
</script>
|
||||||
|
`);
|
||||||
|
|
||||||
|
export function getUsernameById(userId: number): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.get(
|
||||||
|
`SELECT username FROM users WHERE id = ?`,
|
||||||
|
[userId],
|
||||||
|
(err, row: User) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
resolve(row?.username ?? "Unknown");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function posts_string(user?: UserSession): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var out = "";
|
||||||
|
let sql = `
|
||||||
|
SELECT *
|
||||||
|
FROM posts
|
||||||
|
WHERE visibility = 'public'
|
||||||
|
`;
|
||||||
|
let params: any[] = [];
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
sql += `
|
||||||
|
OR (user_id = ?)
|
||||||
|
`;
|
||||||
|
params.push(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ` ORDER BY created_at DESC`;
|
||||||
|
|
||||||
|
db.all(sql, params, async (err, rows: PostRow[]) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
var times = 0;
|
||||||
|
for (const p of rows) {
|
||||||
|
times++;
|
||||||
|
var title = escapeHtml(p.title);
|
||||||
|
var content = escapeHtml(p.content)
|
||||||
|
|
||||||
|
out += `
|
||||||
|
<div class="postlist" onClick="location.href='/posts/${p.hash_id}'">
|
||||||
|
<a href="/posts/${p.hash_id}">${title}</a> ${p.visibility == "public" ? "" : `(${p.visibility})`} <br>
|
||||||
|
<small>by ${await getUsernameById(p.user_id)} (<span id="time_span${times}"></span><script>${parse_date_string}; document.getElementById("time_span${times}").innerHTML = parseDate(new Date(toIso("${p.created_at.replace(" ", "T") + ".000Z"}")));</script>)</small><br><br>
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve(out || "No posts yet.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "upload".')
|
||||||
|
|
||||||
|
app.get("/posts", async (req: Request, res: Response) => {
|
||||||
|
const posts = await posts_string(req.session.user);
|
||||||
|
|
||||||
|
res.send(heads("/posts", "Posts", `
|
||||||
|
${req.session.user ? `
|
||||||
|
<br>
|
||||||
|
<form action="/posts/new" method="GET">
|
||||||
|
<button class="tonly">New Post</button>
|
||||||
|
</form>
|
||||||
|
` : `<p><a href="/login">Log in</a> to create posts.</p>`}
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
<div class="postlist_r">
|
||||||
|
${posts}
|
||||||
|
</div><br><br>
|
||||||
|
`));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/posts/new", (req, res) => {
|
||||||
|
if (!req.session.user)
|
||||||
|
return res.status(401).send(heads("Posts", `
|
||||||
|
<title>New Post</title>
|
||||||
|
<div class="details">
|
||||||
|
<p>You must be logged in to create a post.</p>
|
||||||
|
</div>
|
||||||
|
`, `
|
||||||
|
<a href="/login">Log in</a> | <a href="/register">Register</a><br><br>
|
||||||
|
`))
|
||||||
|
else {
|
||||||
|
return res.send(newpost_string());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/posts", (req, res) => {
|
||||||
|
if (!req.session.user)
|
||||||
|
return res.status(401).send("Login required");
|
||||||
|
var { title, content, visibility, font }: PostRow = req.body;
|
||||||
|
|
||||||
|
switch (font) {
|
||||||
|
case "Verdana":
|
||||||
|
case "monospace":
|
||||||
|
case "Bookman Old Style":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return res.status(400).send(`Invalid font ${font}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashId = GenerateID(12);
|
||||||
|
|
||||||
|
if (process.env.postwrite_html !== "true") {
|
||||||
|
title = escapeHtml(title);
|
||||||
|
content = escapeHtml(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`INSERT INTO posts (user_id, hash_id, title, content, visibility, font)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
|
[req.session.user.id, hashId, title, content, visibility, font],
|
||||||
|
() => res.redirect(`/posts`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// honestly this could just be a get request as well
|
||||||
|
app.post("/posts/:id/delete", (req: Request, res: Response) => {
|
||||||
|
if (!req.session.user) return res.status(401).send("Login required");
|
||||||
|
|
||||||
|
const postId = req.params.id;
|
||||||
|
|
||||||
|
// Check ownership first
|
||||||
|
db.get(`SELECT user_id FROM posts WHERE id = ?`, [postId], (err, row: any) => {
|
||||||
|
if (err) return res.status(500).send("Database error");
|
||||||
|
if (!row) return res.status(404).send("Post not found");
|
||||||
|
|
||||||
|
if (row.user_id !== req.session.user.id) {
|
||||||
|
return res.status(403).send("You cannot delete this post");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bye
|
||||||
|
db.run(`DELETE FROM posts WHERE id = ?`, [postId], (err) => {
|
||||||
|
if (err) return res.status(500).send("Database error");
|
||||||
|
res.redirect("/posts");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/posts/:id", (req, res) => {
|
||||||
|
db.get(
|
||||||
|
`SELECT * FROM posts WHERE hash_id = ?`,
|
||||||
|
[req.params.id],
|
||||||
|
async (err, post: PostRow) => {
|
||||||
|
if (!post) return res.status(404).send(heads("/posts/:", "Posts", "Post not found."));
|
||||||
|
|
||||||
|
if (
|
||||||
|
post.visibility === "private" &&
|
||||||
|
(!req.session.user || req.session.user.id !== post.user_id)
|
||||||
|
) {
|
||||||
|
return res.status(404).send(heads("/posts/:", "Posts", "<br>Post not found.<br>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var font = 'Verdana'
|
||||||
|
if (post.font)
|
||||||
|
font = post.font
|
||||||
|
const user = req.session?.user;
|
||||||
|
|
||||||
|
res.send(heads("/posts/:", null, `
|
||||||
|
<head>
|
||||||
|
<title>${post.title}</title>
|
||||||
|
<style>
|
||||||
|
#font_change_target {
|
||||||
|
font-family: ${font};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<span id="font_change_target" style="font-size: 24px">${post.title}</span><br>
|
||||||
|
by ${await getUsernameById(post.user_id)} ${user && user.id === post.user_id ? `
|
||||||
|
| <form style="display: inline" action="/posts/${post.id}/delete" method="POST">
|
||||||
|
<button type="submit" onclick="return confirm('Delete ${post.title}?')" class="tonly">Delete Post</button>
|
||||||
|
</form>
|
||||||
|
` : ""}<br>
|
||||||
|
<small><span id="time_span"></span></small><br><br>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
${parse_date_string}
|
||||||
|
|
||||||
|
document.getElementById("time_span").innerHTML = parseDate(new Date(toIso("${post.created_at.replace(" ", "T") + ".000Z"}")));
|
||||||
|
</script>
|
||||||
|
<span id="font_change_target">${post.content.replaceAll("\n", "<br>")}</span><br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<a href=/posts>Back</a>
|
||||||
|
<br><br>
|
||||||
|
`, null, true));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from "../util/Logger";
|
||||||
|
import "../updateInfo";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import { db } from "../util/database";
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import styles_string from "../components/styles";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const c = new Logger("Server(Pages)", "gray");
|
||||||
|
|
||||||
|
function register_string(alert: string = "<br>"): string {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${process.env.servicename || "File Server"} | Registration</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
${styles_string()}
|
||||||
|
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<p>Server region: ${"(alternate) " + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr><br>
|
||||||
|
|
||||||
|
<span class="description">
|
||||||
|
Registration
|
||||||
|
</span>
|
||||||
|
|
||||||
|
${`<p id="alerts">${alert}</p>` ? alert : ""}
|
||||||
|
|
||||||
|
<div class="form-box">
|
||||||
|
<form class="post-form" method="POST" action="/register">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" name="username" required><br>
|
||||||
|
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" name="password" required><br>
|
||||||
|
|
||||||
|
<button type="submit">Register</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
${pages_string("/register")}
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
}
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "register".');
|
||||||
|
|
||||||
|
app.use(require("express").urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
app.get("/register", async (req: Request, res: Response) => {
|
||||||
|
if (req.session.user)
|
||||||
|
res.redirect("/")
|
||||||
|
else
|
||||||
|
res.send(register_string());
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/register", async (req: Request, res: Response) => {
|
||||||
|
if (process.env.enable_register == "true") {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.send(register_string("Missing username or password."));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
|
||||||
|
[username, passwordHash],
|
||||||
|
(err: any) => {
|
||||||
|
var isnameerror = false;
|
||||||
|
if (err) {
|
||||||
|
if (err.message.includes("UNIQUE")) {
|
||||||
|
isnameerror = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isnameerror)
|
||||||
|
c.log(`User registered: ${username}`);
|
||||||
|
|
||||||
|
if (isnameerror)
|
||||||
|
res.send(register_string(`The username ${username} is already taken!`))
|
||||||
|
else
|
||||||
|
res.send(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 50
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-bottom: 10%;
|
||||||
|
text-align: left;
|
||||||
|
color: black;
|
||||||
|
font-family: Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #3c72a3
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #2a567d
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #383737;
|
||||||
|
color: #b0acac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra {
|
||||||
|
font-size: 24px
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
<div class="details">
|
||||||
|
<p>Server region: ${'(alternate) ' + process.env.altregion}</p>
|
||||||
|
</div><hr><br>
|
||||||
|
<div class="description">
|
||||||
|
${process.env.description || "A simple file server built with Express."}
|
||||||
|
</div>
|
||||||
|
<div class="extra">
|
||||||
|
Account created successfully!<br>
|
||||||
|
${req.session.user ? `
|
||||||
|
<p>Logged in as ${req.session.user.username}<br>
|
||||||
|
<a href=/api/logout>log out</a></p>
|
||||||
|
` : `
|
||||||
|
<a href=/login>Log in</a> | <a href=/register>Register</a><br>
|
||||||
|
`}
|
||||||
|
<br><br>
|
||||||
|
Pages<br>
|
||||||
|
) <a href=/staticfiles>/staticfiles</a><br>
|
||||||
|
) <a href="/">Home</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
c.error("Hashing error");
|
||||||
|
res.status(500).send("Internal server error.");
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
res.status(401).send(register_string(`${process.env.servicename || "File Server"} does not support account registration.`))
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Express, Request, Response, json } from "express";
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
import '../updateInfo'
|
||||||
|
var c = new Logger("Server(Pages)", "gray")
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import styles_string from "../components/styles";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "root".')
|
||||||
|
|
||||||
|
app.get('/', async (req: Request, res: Response) => {
|
||||||
|
res.send(
|
||||||
|
/*`
|
||||||
|
|
||||||
|
Update server for Journey to Nowhere. Local version: ${require('../gameinfo').GameVersion}
|
||||||
|
<br>Server region: ${ipd?.country || '(alternate) ' + process.env.altregion}
|
||||||
|
<br>Server ping: ${latency}ms
|
||||||
|
|
||||||
|
`*/
|
||||||
|
`<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>${process.env.servicename || "File Server"}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
${styles_string()}
|
||||||
|
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
<div class="details">
|
||||||
|
${/*<p class="details">Local version: ${require('../updateInfo').GameVersion}</p>*/""}
|
||||||
|
<p>Server region: ${'(alternate) ' + process.env.altregion}</p>
|
||||||
|
</div><hr><br>
|
||||||
|
<div class="description">
|
||||||
|
${process.env.description || "A simple file server built with Express."}
|
||||||
|
</div>
|
||||||
|
<br>${req.session.user ? `
|
||||||
|
Logged in as ${req.session.user.username}.
|
||||||
|
<button class="tonly" onClick="if (confirm('Are you sure you want to log out of ${req.session.user.username}?')) location.href = '/api/logout'">Log out</button></p>
|
||||||
|
` : `
|
||||||
|
<a href=/login>Log in</a> | <a href=/register>Register</a><br>
|
||||||
|
`}
|
||||||
|
<br>
|
||||||
|
<div class="extra">
|
||||||
|
${pages_string("/")}
|
||||||
|
</div>
|
||||||
|
</body>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import { Express, Request, Response } from "express";
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
var c = new Logger("Server(Pages)", "gray")
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { upload } from "..";
|
||||||
|
import * as dbs from '../util/database'
|
||||||
|
import { User, UserSession } from "../types/User";
|
||||||
|
const db = dbs.db_file;
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const upload_string = (alert: string = null, formreplace: string = null, files: string = null): string => {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${process.env.servicename || "File Server"} | Uploads</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${styles_string()}
|
||||||
|
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<p>Server region: ${"(alternate) " + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr><br>
|
||||||
|
|
||||||
|
<div class="description">
|
||||||
|
Upload
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="alerts">${alert ? alert : ""}</p>
|
||||||
|
|
||||||
|
${formreplace ? formreplace : `<div class="form-box">
|
||||||
|
<form action="/upload" method="POST" enctype="multipart/form-data">
|
||||||
|
<input style="font-size: 16px" class="tonly-formf" type="file" name="file" required />
|
||||||
|
<button style="font-size: 16px" class="tonly" type="submit">Upload</button>
|
||||||
|
</form>
|
||||||
|
</div>`}
|
||||||
|
${formreplace ? "" : "<br>"}
|
||||||
|
<h2>All Uploads</h2>
|
||||||
|
${files ? files : "None yet!<br>"}<br>
|
||||||
|
|
||||||
|
${pages_string("/upload")}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload_string_home = async (alert: string, formreplace: string = null, user: UserSession): Promise<string> => {
|
||||||
|
var files = await files_string(user)
|
||||||
|
return upload_string(alert, formreplace, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function files_string(user: UserSession): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let out = "";
|
||||||
|
let filect = 1;
|
||||||
|
|
||||||
|
db.all(
|
||||||
|
`SELECT * FROM files WHERE user_id = ? ORDER BY upload_date DESC`,
|
||||||
|
[user.id],
|
||||||
|
(err, files) => {
|
||||||
|
if (err) {
|
||||||
|
return reject("database error");
|
||||||
|
}
|
||||||
|
var times = 0;
|
||||||
|
files.forEach((f: any) => {
|
||||||
|
times++;
|
||||||
|
out += `
|
||||||
|
<form method="POST" action="/delete" style="display:inline;">
|
||||||
|
${filect}) <a href="/uploads/${f.stored_name}" target="_blank">${f.original_name}</a> - uploaded on
|
||||||
|
<span id="time_span${times}"></span>
|
||||||
|
<script>
|
||||||
|
${parse_date_string}
|
||||||
|
document.getElementById("time_span${times}").innerHTML = parseDate(new Date(toIso("${f.upload_date.replace(" ", "T") + ".000Z"}")), false, true);
|
||||||
|
</script>
|
||||||
|
<input type="hidden" name="fileId" value="${f.id}">
|
||||||
|
| <button class="tonly" onclick="return confirm('Delete ${f.original_name}?')" type="submit">Delete</button>
|
||||||
|
</form><br>
|
||||||
|
`;
|
||||||
|
filect++;
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(out);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import styles_string, { parse_date_string } from "../components/styles";
|
||||||
|
import pages_string from "../components/pages_text";
|
||||||
|
import { convertToIso, parseDate } from "../util/dateParse";
|
||||||
|
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
c.log('Found page "upload".')
|
||||||
|
|
||||||
|
app.post("/delete", (req, res) => {
|
||||||
|
if (!req.session.user) {
|
||||||
|
return res.status(401).send(upload_string("", "You must be logged in to use this feature."))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const { fileId } = req.body;
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
|
||||||
|
db.get(
|
||||||
|
`SELECT * FROM files WHERE id = ? AND user_id = ?`,
|
||||||
|
[fileId, userId],
|
||||||
|
(err, file: any) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).send("Database error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return res.status(403).send("You are not authorized to delete this file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join("uploads", file.stored_name);
|
||||||
|
fs.unlink(filePath, (fsErr) => {
|
||||||
|
if (fsErr && fsErr.code !== "ENOENT") {
|
||||||
|
console.error("Filesystem delete error:", fsErr);
|
||||||
|
return res.status(500).send("Error deleting file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`DELETE FROM files WHERE id = ?`,
|
||||||
|
[fileId],
|
||||||
|
(delErr) => {
|
||||||
|
if (delErr) {
|
||||||
|
return res.status(500).send("Database delete error.");
|
||||||
|
}
|
||||||
|
res.redirect("/upload");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.get("/upload", async (req: Request, res: Response) => {
|
||||||
|
if (!req.session.user)
|
||||||
|
return res.status(401).send(upload_string("", "You must be <a href=/login>logged in</a> to use this feature."))
|
||||||
|
else {
|
||||||
|
const user = req.session.user;
|
||||||
|
return res.send(await upload_string_home(null, null, user))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/upload", upload.single("file"), async (req: Request, res: Response) => {
|
||||||
|
if (!req.session.user)
|
||||||
|
return res.status(401).send(upload_string("", "You must be logged in to use this feature."))
|
||||||
|
else {
|
||||||
|
const user = req.session.user;
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).send(await upload_string_home("No file uploaded.", null, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO files (user_id, original_name, stored_name)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`;
|
||||||
|
db.run(sql, [user.id, req.file.originalname, req.file.filename], async (err) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).send("Database error");
|
||||||
|
}
|
||||||
|
res.status(200).send(await upload_string_home(`Uploaded ${req.file.originalname}!`, null, user))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Express, Request, Response } from 'express'
|
||||||
|
import Logger from '../util/Logger'
|
||||||
|
const ce = new Logger("Server(Uploads)", "gray");
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import styles_string, { parse_date_string } from '../components/styles';
|
||||||
|
import pages_string from '../components/pages_text';
|
||||||
|
import { getUsernameById } from './posts';
|
||||||
|
import { db_file } from '../util/database';
|
||||||
|
import { Upload } from '../types/Upload';
|
||||||
|
import { convertToIso, parseDate } from '../util/dateParse';
|
||||||
|
import { bytesToSize } from '../util';
|
||||||
|
|
||||||
|
function getUploadByFilename(filename: string): Promise<Upload> {
|
||||||
|
try {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db_file.get(
|
||||||
|
`
|
||||||
|
SELECT files.original_name, files.stored_name, files.upload_date, files.user_id
|
||||||
|
FROM main.files
|
||||||
|
JOIN udb.users ON udb.users.id = files.user_id
|
||||||
|
WHERE files.stored_name = ?
|
||||||
|
`,
|
||||||
|
[filename],
|
||||||
|
(err, row: Upload) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(row ?? null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
// if the cb fails somehow
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Main(app: Express) {
|
||||||
|
var cleanPath = path.join(process.cwd(), "uploads")
|
||||||
|
|
||||||
|
ce.log(`Processing uploads...`)
|
||||||
|
|
||||||
|
app.get('/uploads/:filename', async (req: Request, res: Response) => {
|
||||||
|
var filename: string = req.params.filename;
|
||||||
|
var direct = req.query.direct === "true" ? true : false || false;
|
||||||
|
var dl = req.query.dl === "true" ? true : false || false;
|
||||||
|
var fullPath: string = path.join(cleanPath, filename);
|
||||||
|
var useUploadInfo = true;
|
||||||
|
if (fs.existsSync(fullPath)) {
|
||||||
|
const stat = fs.statSync(fullPath)
|
||||||
|
const size = bytesToSize(stat.size)
|
||||||
|
|
||||||
|
var upload: Upload = null;
|
||||||
|
upload = await getUploadByFilename(filename);
|
||||||
|
if (!upload)
|
||||||
|
useUploadInfo = false;
|
||||||
|
|
||||||
|
if (direct) {
|
||||||
|
res.status(200).sendFile(fullPath)
|
||||||
|
} else if (dl) {
|
||||||
|
res.status(200).download(fullPath)
|
||||||
|
} else {
|
||||||
|
res.status(200).send(`<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>${useUploadInfo ? upload.original_name : filename}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${styles_string()}
|
||||||
|
<h1>${process.env.servicename || "File Server"}</h1>
|
||||||
|
<div class="details">
|
||||||
|
${/*<p class="details">Local version: ${require('../updateInfo').GameVersion}</p>*/""}
|
||||||
|
<p>Server region: ${'(alternate) ' + process.env.altregion}</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span style="font-size: 24px">${useUploadInfo ? upload.original_name : filename}</span> ${size}
|
||||||
|
<br>${useUploadInfo ? `uploaded by ${await getUsernameById(upload.user_id)} at <span id="time_span"></span>
|
||||||
|
<script>
|
||||||
|
${parse_date_string}
|
||||||
|
document.getElementById("time_span").innerHTML = parseDate(new Date(toIso("${upload.upload_date.replace(" ", "T") + ".000Z"}")), false, true);
|
||||||
|
</script><br>` : ""}<br>
|
||||||
|
${/*<br>${useUploadInfo ? `uploaded by ${await getUsernameById(upload.user_id)} at ${parseDate(upload.upload_date, false, true)}<br>` : ""}<br>*/""}
|
||||||
|
<div style="font-size: 18px;">
|
||||||
|
<a href='/uploads/${filename}?direct=true'><button class="tonly">Open file</button></a>
|
||||||
|
|
||||||
|
<a href='/uploads/${filename}?dl=true'><button class="tonly">Download file</button></a>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
${pages_string("/pvfiles")}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
res.status(404).send(
|
||||||
|
`File not found.`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface ConsoleCommandData {
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
usage: string | boolean,
|
||||||
|
aliases?: string[] // should be boolean but makes issues
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface PostRow {
|
||||||
|
id: number
|
||||||
|
hash_id: string
|
||||||
|
user_id: number
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
visibility: "public" | "unlisted" | "private"
|
||||||
|
font: 'Verdana' | 'monospace' | 'Bookman Old Style'
|
||||||
|
created_at: string
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Upload {
|
||||||
|
id: number,
|
||||||
|
user_id: number,
|
||||||
|
original_name: string,
|
||||||
|
stored_name: string,
|
||||||
|
upload_date: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password_hash: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSession {
|
||||||
|
id: number,
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import "express-session";
|
||||||
|
|
||||||
|
declare module "express-session" {
|
||||||
|
interface SessionData {
|
||||||
|
user?: {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'colorts/lib/string';
|
||||||
|
|
||||||
|
export enum VerboseLevel {
|
||||||
|
NONE = 0, // No logging except for errors
|
||||||
|
WARNS = 1, // Log warns
|
||||||
|
ALL = 2, // Warns and (useless) debug
|
||||||
|
VERBL = 3, // Warns, debug and verbose
|
||||||
|
VERBH = 4, // Warns, debug, verbose and very verbose (thanks copilot this is so funny)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Color = 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray' | 'black' | 'italic' | 'bold' | 'underline' | 'strikethrough' | 'inverse' | 'bgRed' | 'bgGreen' | 'bgYellow' | 'bgBlue' | 'bgMagenta' | 'bgCyan' | 'bgWhite' | 'bgBlack' | 'bgGray' | 'bgItalic';
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
public static VERBOSE_LEVEL: VerboseLevel = 1;
|
||||||
|
|
||||||
|
constructor(public name: string, public color: Color = 'blue') {
|
||||||
|
this.name = name;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDate(): string {
|
||||||
|
return new Date().toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private raw(...args: string[]) {
|
||||||
|
// @ts-ignore - Element implicitly has an 'any' type because index expression is not of type 'number'
|
||||||
|
console.log(`[${this.getDate().white.bold}] <${this.name[this.color].bold}>`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(...args: string[]) {
|
||||||
|
this.raw(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public trail(...args: any[]) {
|
||||||
|
console.log(`\t↳ ${args.join(' ').gray}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(e: Error | string, stack: boolean = true) {
|
||||||
|
if (typeof e === 'string') e = new Error(e);
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`ERROR<${this.name}>`.bgRed.bold}`, e.message);
|
||||||
|
if (e.stack && stack) this.trail(e.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(...args: string[]) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.WARNS) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`WARN<${this.name}>`.bgYellow.bold}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.ALL) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`DEBUG<${this.name}>`.bgBlue.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public verbL(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.VERBL) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`VERBL<${this.name}>`.bgCyan.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public verbH(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.VERBH) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`VERBH<${this.name}>`.bgCyan.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import 'colorts/lib/string';
|
||||||
|
// crepesr ♥
|
||||||
|
|
||||||
|
|
||||||
|
export enum VerboseLevel {
|
||||||
|
NONE = 0, // No logging except for errors
|
||||||
|
WARNS = 1, // Log warns
|
||||||
|
ALL = 2, // Warns and (useless) debug
|
||||||
|
VERBL = 3, // Warns, debug and verbose
|
||||||
|
VERBH = 4, // Warns, debug, verbose and very verbose (thanks copilot this is so funny)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Color = 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray' | 'black' | 'italic' | 'bold' | 'underline' | 'strikethrough' | 'inverse' | 'bgRed' | 'bgGreen' | 'bgYellow' | 'bgBlue' | 'bgMagenta' | 'bgCyan' | 'bgWhite' | 'bgBlack' | 'bgGray' | 'bgItalic';
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
public static VERBOSE_LEVEL: VerboseLevel = 1;
|
||||||
|
|
||||||
|
constructor(public name: string, public color: Color = 'blue') {
|
||||||
|
this.name = name;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDate(): string {
|
||||||
|
return new Date().toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private raw(...args: string[]) {
|
||||||
|
// @ts-ignore - Element implicitly has an 'any' type because index expression is not of type 'number'
|
||||||
|
console.log(`[${this.getDate().white.bold}] <${this.name[this.color].bold}>`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(...args: string[]) {
|
||||||
|
this.raw(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public trail(...args: any[]) {
|
||||||
|
console.log(`\t↳ ${args.join(' ').gray}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(e: Error | string, stack: boolean = true) {
|
||||||
|
if (typeof e === 'string') e = new Error(e);
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`ERROR<${this.name}>`.bgRed.bold}`, e.message);
|
||||||
|
if (e.stack && stack) this.trail(e.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(...args: string[]) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.WARNS) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`WARN<${this.name}>`.bgYellow.bold}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.ALL) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`DEBUG<${this.name}>`.bgBlue.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public verbL(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.VERBL) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`VERBL<${this.name}>`.bgCyan.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public verbH(...args: any) {
|
||||||
|
if (Logger.VERBOSE_LEVEL < VerboseLevel.VERBH) return;
|
||||||
|
console.log(`[${this.getDate().white.bold}] ${`VERBH<${this.name}>`.bgCyan.bold}`, ...args);
|
||||||
|
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
// import from jtw 🤤
|
||||||
|
// all log comments here r for debug dont remove
|
||||||
|
|
||||||
|
|
||||||
|
import readline from 'readline'
|
||||||
|
import Logger from './Logger'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { ConsoleCommandData } from '../types/Command'
|
||||||
|
|
||||||
|
var c = new Logger("COMMAND", "yellow")
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin as unknown as NodeJS.ReadableStream,
|
||||||
|
output: process.stdout as unknown as NodeJS.WritableStream
|
||||||
|
})
|
||||||
|
|
||||||
|
const originalWrite = process.stdout.write.bind(process.stdout)
|
||||||
|
let inWritePatch = false
|
||||||
|
|
||||||
|
process.stdout.write = (chunk: any, encoding?: any, cb?: any) => {
|
||||||
|
if (inWritePatch) {
|
||||||
|
// Don't recurse
|
||||||
|
return originalWrite(chunk, encoding, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
inWritePatch = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
readline.clearLine(process.stdout as unknown as NodeJS.WritableStream, 0)
|
||||||
|
readline.cursorTo(process.stdout as unknown as NodeJS.WritableStream, 0)
|
||||||
|
|
||||||
|
const result = originalWrite(chunk, encoding, cb)
|
||||||
|
|
||||||
|
rl.prompt(true)
|
||||||
|
return result
|
||||||
|
} finally {
|
||||||
|
inWritePatch = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
export function splitStr(q: string): Array<string> {
|
||||||
|
return q.split(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// clears last printed line
|
||||||
|
// right now this just prints another line...
|
||||||
|
export function removeLine() {
|
||||||
|
readline.moveCursor(process.stdout as unknown as NodeJS.WritableStream, 0, -1)
|
||||||
|
readline.clearLine(process.stdout as unknown as NodeJS.WritableStream, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// repeat
|
||||||
|
export function Prompt() {
|
||||||
|
rl.prompt()
|
||||||
|
|
||||||
|
rl.once("line", cmd => {
|
||||||
|
if (!cmd.trim()) {
|
||||||
|
Prompt()
|
||||||
|
} else {
|
||||||
|
HandlePrompt(splitStr(cmd.trim()))
|
||||||
|
Prompt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Clear: string = "$clear"
|
||||||
|
export var CommandAliases: ConsoleCommandData[] = []
|
||||||
|
export var CommandFiles: string[] = []
|
||||||
|
export var AllFilesProcessed: boolean = false
|
||||||
|
export var AllAliasesProcessed: boolean = false
|
||||||
|
|
||||||
|
function removePreviousLine() {
|
||||||
|
readline.moveCursor(process.stdout, 0, -1);
|
||||||
|
readline.clearLine(process.stdout, 0);
|
||||||
|
readline.cursorTo(process.stdout, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function HandlePrompt(args: Array<string>) {
|
||||||
|
let inputName = args[0]//.toLowerCase() // i don't know why i did this this is actually why setpvf was removed
|
||||||
|
let cmdArgs: Array<string> = args.slice(1)
|
||||||
|
let error = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
removePreviousLine();
|
||||||
|
console.log(`> ${inputName} ${cmdArgs.join(' ')}`);
|
||||||
|
const directoryPath = `${process.cwd()}/output/src/cmd/`; // Replace with your directory path
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(directoryPath, 'utf-8');
|
||||||
|
//c.log(`HI ${files[0]}`)
|
||||||
|
if (!AllFilesProcessed)
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
var file = files[i]
|
||||||
|
let check = true
|
||||||
|
for (var file2 in CommandFiles)
|
||||||
|
if (file2.includes(file))
|
||||||
|
check = false
|
||||||
|
|
||||||
|
if (!file.includes('.map') && check) {
|
||||||
|
//c.log(file)
|
||||||
|
CommandFiles.push(file.replace(".ts", ".js"))
|
||||||
|
//c.log(`${file}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AllFilesProcessed = true
|
||||||
|
//c.log(CommandFiles.length.toString() + " CommandFile")
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading directory:', err);
|
||||||
|
}
|
||||||
|
if (!AllAliasesProcessed)
|
||||||
|
for (let i = 0; i < CommandFiles.length; i++) {
|
||||||
|
var CommandFile = CommandFiles[i]
|
||||||
|
const file: ConsoleCommandData = require(`${process.cwd()}/output/src/cmd/${CommandFile}`).metadata
|
||||||
|
//c.log(file.name)
|
||||||
|
if (file.aliases)
|
||||||
|
CommandAliases.push(file)
|
||||||
|
AllAliasesProcessed = true
|
||||||
|
}
|
||||||
|
for (let i = 0; i < CommandAliases.length; i++) {
|
||||||
|
const cmd = CommandAliases[i];
|
||||||
|
|
||||||
|
if (cmd.aliases) {
|
||||||
|
//c.log('checked aaa')
|
||||||
|
for (let i = 0; i < cmd.aliases.length; i++) {
|
||||||
|
var alias = cmd.aliases[i]
|
||||||
|
//c.log(alias)
|
||||||
|
if (alias === inputName)
|
||||||
|
inputName = cmd.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cs = new Logger(`CLIENT/${inputName}.js+resp`)
|
||||||
|
|
||||||
|
//c.log(CommandAliases.length.toString() + " Alias")
|
||||||
|
let response = await require(`${process.cwd()}/output/src/cmd/${inputName}.js`).Main(cmdArgs)
|
||||||
|
|
||||||
|
if (response === Clear)
|
||||||
|
console.clear()
|
||||||
|
else if (response == 0 || response == 1 || !response)
|
||||||
|
"" // do nothing, an error occured.
|
||||||
|
else
|
||||||
|
cs.log(response)
|
||||||
|
} catch (err) {
|
||||||
|
error = true
|
||||||
|
if (err.toString().includes("Cannot find module"))
|
||||||
|
c.log(`Command ${inputName} not found.`)
|
||||||
|
else
|
||||||
|
c.error(`${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// import from jtw 🤤
|
||||||
|
// all log comments here r for debug dont remove
|
||||||
|
|
||||||
|
|
||||||
|
import readline from 'readline'
|
||||||
|
import Logger from './Logger'
|
||||||
|
import { ConsoleCommandData } from '../types/Command'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
var c = new Logger("COMMAND", "yellow")
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin as unknown as NodeJS.ReadableStream,
|
||||||
|
output: process.stdout as unknown as NodeJS.WritableStream
|
||||||
|
})
|
||||||
|
|
||||||
|
const originalWrite = process.stdout.write.bind(process.stdout)
|
||||||
|
let inWritePatch = false
|
||||||
|
|
||||||
|
process.stdout.write = (chunk: any, encoding?: any, cb?: any) => {
|
||||||
|
if (inWritePatch) {
|
||||||
|
// Don't recurse
|
||||||
|
return originalWrite(chunk, encoding, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
inWritePatch = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
readline.clearLine(process.stdout as unknown as NodeJS.WritableStream, 0)
|
||||||
|
readline.cursorTo(process.stdout as unknown as NodeJS.WritableStream, 0)
|
||||||
|
|
||||||
|
const result = originalWrite(chunk, encoding, cb)
|
||||||
|
|
||||||
|
rl.prompt(true)
|
||||||
|
return result
|
||||||
|
} finally {
|
||||||
|
inWritePatch = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
export function splitStr(q: string): Array<string> {
|
||||||
|
return q.split(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// clears last printed line
|
||||||
|
// right now this just prints another line...
|
||||||
|
export function removeLine() {
|
||||||
|
readline.moveCursor(process.stdout as unknown as NodeJS.WritableStream, 0, -1)
|
||||||
|
readline.clearLine(process.stdout as unknown as NodeJS.WritableStream, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// repeat
|
||||||
|
export function Prompt() {
|
||||||
|
rl.prompt()
|
||||||
|
|
||||||
|
rl.once("line", async (cmd) => {
|
||||||
|
if (!cmd.trim()) {
|
||||||
|
Prompt()
|
||||||
|
} else {
|
||||||
|
await HandlePrompt(splitStr(cmd.trim()))
|
||||||
|
Prompt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// For exit/Runtime
|
||||||
|
export function EmergencyPrompt() {
|
||||||
|
rl.prompt()
|
||||||
|
|
||||||
|
rl.once("line", async (cmd) => {
|
||||||
|
if (!cmd.trim()) {
|
||||||
|
Prompt()
|
||||||
|
} else {
|
||||||
|
if (splitStr(cmd.trim())[0].toLowerCase() === 'exit')
|
||||||
|
process.exit(0)
|
||||||
|
Prompt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Clear: string = "$clear"
|
||||||
|
export var CommandAliases: ConsoleCommandData[] = []
|
||||||
|
export var CommandFiles: string[] = []
|
||||||
|
export var AllFilesProcessed: boolean = false
|
||||||
|
export var AllAliasesProcessed: boolean = false
|
||||||
|
|
||||||
|
export async function HandlePrompt(args: Array<string>) {
|
||||||
|
let inputName = args[0].toLowerCase()
|
||||||
|
let cmdArgs: Array<string> = args.slice(1)
|
||||||
|
let error = false
|
||||||
|
//c.log(`${process.cwd()}`)
|
||||||
|
try {
|
||||||
|
let cs = new Logger(`CLIENT/${inputName}.js+resp`)
|
||||||
|
c.log(`${inputName} ${cmdArgs.join(' ')}`)
|
||||||
|
const directoryPath = process.cwd() + '/output/src/cmd'; // Replace with your directory path
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(directoryPath, 'utf-8');
|
||||||
|
//c.log(`HI ${files[0]}`)
|
||||||
|
if (!AllFilesProcessed)
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
var file = files[i]
|
||||||
|
let check = true
|
||||||
|
for (var file2 in CommandFiles)
|
||||||
|
if (file2.includes(file))
|
||||||
|
check = false
|
||||||
|
|
||||||
|
if (!file.includes('.map') && check) {
|
||||||
|
//c.log(file)
|
||||||
|
CommandFiles.push(file.replace(".ts", ".js"))
|
||||||
|
//c.log(`${file}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AllFilesProcessed = true
|
||||||
|
//c.log(CommandFiles.length.toString() + " CommandFile")
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading directory:', err);
|
||||||
|
}
|
||||||
|
if (!AllAliasesProcessed)
|
||||||
|
for (let i = 0; i < CommandFiles.length; i++) {
|
||||||
|
var CommandFile = CommandFiles[i]
|
||||||
|
const file: ConsoleCommandData = require(`${process.cwd()}/output/src/cmd/${CommandFile}`).metadata
|
||||||
|
//c.log(file.name)
|
||||||
|
if (file.aliases)
|
||||||
|
CommandAliases.push(file)
|
||||||
|
AllAliasesProcessed = true
|
||||||
|
}
|
||||||
|
for (let i = 0; i < CommandAliases.length; i++) {
|
||||||
|
const cmd = CommandAliases[i];
|
||||||
|
|
||||||
|
if (cmd.aliases) {
|
||||||
|
//c.log('checked aaa')
|
||||||
|
for (let i = 0; i < cmd.aliases.length; i++) {
|
||||||
|
var alias = cmd.aliases[i]
|
||||||
|
//c.log(alias)
|
||||||
|
if (alias === inputName)
|
||||||
|
inputName = cmd.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//c.log(CommandAliases.length.toString() + " Alias")
|
||||||
|
let response = await require(`${process.cwd()}/output/src/cmd/${inputName}.js`).Main(cmdArgs)
|
||||||
|
|
||||||
|
if (response === Clear)
|
||||||
|
console.clear()
|
||||||
|
else if (response == 0 || response == 1)
|
||||||
|
"" // do nothing, an error occured.
|
||||||
|
else
|
||||||
|
cs.log(response)
|
||||||
|
} catch (err) {
|
||||||
|
//@ts-ignore
|
||||||
|
if (err.toString().includes("Cannot find module" || "ENOENT"))
|
||||||
|
c.log(`Command ${inputName} not found.`)
|
||||||
|
else
|
||||||
|
c.error(`${err}`)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const Reload = () => setTimeout(function () {
|
||||||
|
process.on("exit", function () {
|
||||||
|
require("child_process").spawn(process.argv.shift(), process.argv, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
detached : true,
|
||||||
|
stdio: "inherit"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
process.exit();
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
export default Reload;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import fs from 'fs/promises'
|
||||||
|
import PasswordGen from './passwordGen'
|
||||||
|
import Logger from './Logger'
|
||||||
|
const l = new Logger("Server(PvfPw)", "gray")
|
||||||
|
|
||||||
|
export const getFileNameFromPath = (path: string) => {
|
||||||
|
return path.replace(/^.*[\\/]/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SetRndPasswd(OriginalFilePath: string): string | void {
|
||||||
|
const Password = PasswordGen(48);
|
||||||
|
|
||||||
|
fs.writeFile(`${OriginalFilePath}.acck`, Password)
|
||||||
|
//l.log(`Wrote password for ${getFileNameFromPath(OriginalFilePath)}.passwd: ${Password} `)
|
||||||
|
l.log(`Wrote AccessKey for ${OriginalFilePath}`)
|
||||||
|
|
||||||
|
return Password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import sqlite3, { Database } from "sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
var db: Database = null;
|
||||||
|
var db_file: Database = null;
|
||||||
|
var failed = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbPath = path.join(process.cwd(), "users.db");
|
||||||
|
db = new sqlite3.Database(dbPath);
|
||||||
|
db.serialize(() => {
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
hash_id TEXT UNIQUE,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
visibility TEXT NOT NULL DEFAULT 'public',
|
||||||
|
font TEXT NOT NULL DEFAULT 'Verdana',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbPath_f = path.join(process.cwd(), "files.db")
|
||||||
|
db_file = new sqlite3.Database(dbPath_f);
|
||||||
|
|
||||||
|
db_file.serialize(() => {
|
||||||
|
db_file.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS files (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
original_name TEXT NOT NULL,
|
||||||
|
stored_name TEXT NOT NULL,
|
||||||
|
upload_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
db_file.run(
|
||||||
|
`ATTACH DATABASE ? AS udb`,
|
||||||
|
[path.join(process.cwd(), "users.db")]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
failed = true;
|
||||||
|
console.error(`Failed to initialize databases:\n${err}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
db, db_file
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
export function parseDate(input: string, useRtf = true, showTime: boolean = true, timeZone?: string): string {
|
||||||
|
const date = new Date(input.replace(" ", "T"));
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const tz = timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
const diffSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
if (useRtf) {
|
||||||
|
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||||
|
|
||||||
|
if (diffSeconds < 60) return rtf.format(-diffSeconds, "second");
|
||||||
|
if (diffSeconds < 3600) return rtf.format(-Math.floor(diffSeconds / 60), "minute");
|
||||||
|
if (diffSeconds < 86400) return rtf.format(-Math.floor(diffSeconds / 3600), "hour");
|
||||||
|
if (diffSeconds < 604800) return rtf.format(-Math.floor(diffSeconds / 86400), "day");
|
||||||
|
}
|
||||||
|
return date.toLocaleString("en-US", {
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
...(showTime && {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true,
|
||||||
|
timeZoneName: "short"
|
||||||
|
}),
|
||||||
|
timeZone: tz
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
export function convertToIso(dbTime: string, serverZone: string): string {
|
||||||
|
const zone = serverZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
const dt = DateTime.fromFormat(dbTime, "yyyy-MM-dd HH:mm:ss", { zone });
|
||||||
|
|
||||||
|
return dt.toISO();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import sqlite3, { Database } from "sqlite3";
|
||||||
|
|
||||||
|
export function db(dbname: string = null, dbliteral: Database) {
|
||||||
|
if (dbliteral)
|
||||||
|
var db = dbliteral
|
||||||
|
else
|
||||||
|
var db = new sqlite3.Database(dbname);
|
||||||
|
db.all("PRAGMA database_list;", (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Error getting DB list:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Attached databases:");
|
||||||
|
rows.forEach((row: any) => {
|
||||||
|
console.log(`- name: ${row.name}, file: ${row.file}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import crypto from "crypto"
|
||||||
|
import Logger from "./Logger"
|
||||||
|
|
||||||
|
const c = new Logger("Server(Pwd)")
|
||||||
|
|
||||||
|
export default function GenerateID(bs: number): string {
|
||||||
|
try {
|
||||||
|
const buffer = crypto.randomBytes(bs)
|
||||||
|
|
||||||
|
let id = buffer.toString("base64")
|
||||||
|
|
||||||
|
id = id.replace(/[+/=]/g, "")
|
||||||
|
|
||||||
|
return id || "Failed to generate an ID"
|
||||||
|
} catch (err) {
|
||||||
|
c.log(
|
||||||
|
"Server cannot generate a unique ID."
|
||||||
|
)
|
||||||
|
return "Failed to generate an ID"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
export function parseInt(args: any): any {
|
||||||
|
const n = Number.parseInt(args)
|
||||||
|
if (Number.isInteger(n))
|
||||||
|
return n
|
||||||
|
else
|
||||||
|
throw new Error(`${args} is not an integer`) // so its easier
|
||||||
|
}
|
||||||
|
|
||||||
|
export function escapeHtml(input: string): string {
|
||||||
|
return input
|
||||||
|
.replaceAll(/&/g, "&")
|
||||||
|
.replaceAll(/</g, "<")
|
||||||
|
.replaceAll(/>/g, ">")
|
||||||
|
.replaceAll(/"/g, """)
|
||||||
|
.replaceAll(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToSize(bytes: number, decimals = 2) {
|
||||||
|
if (!Number(bytes)) {
|
||||||
|
return '0 B';
|
||||||
|
}
|
||||||
|
|
||||||
|
const kbToBytes = 1000;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = [
|
||||||
|
'B',
|
||||||
|
'KB',
|
||||||
|
'MB',
|
||||||
|
'GB',
|
||||||
|
'TB',
|
||||||
|
'PB',
|
||||||
|
'EB',
|
||||||
|
'ZB',
|
||||||
|
'YB',
|
||||||
|
];
|
||||||
|
|
||||||
|
const index = Math.floor(
|
||||||
|
Math.log(bytes) / Math.log(kbToBytes),
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${parseFloat(
|
||||||
|
(bytes / Math.pow(kbToBytes, index)).toFixed(dm),
|
||||||
|
)} ${sizes[index]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitUntil(conditionFn: () => boolean, interval = 100): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
try {
|
||||||
|
if (conditionFn()) {
|
||||||
|
clearInterval(timer);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
export function mkDirManIfNotExists(path: string): boolean {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(path))
|
||||||
|
fs.mkdir(path, () => { return true });
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mkDirsManIfNotExists(paths: string[]): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
var pass = false
|
||||||
|
|
||||||
|
for (const path in paths) {
|
||||||
|
await waitUntil(() => pass = true)
|
||||||
|
|
||||||
|
pass = false
|
||||||
|
if (!fs.existsSync(path))
|
||||||
|
fs.mkdir(path, () => pass = true);
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import chp from 'child_process'
|
||||||
|
import Logger from './Logger'
|
||||||
|
import GenerateID from './idGen'
|
||||||
|
const c = new Logger("Server(Pwd)")
|
||||||
|
|
||||||
|
// ill replace this later....
|
||||||
|
export default function PasswordGen(bs: number): string {
|
||||||
|
try {
|
||||||
|
const cmdOutput: string = GenerateID(bs);
|
||||||
|
if (!cmdOutput) {
|
||||||
|
c.log(`Password generate output is empty...`)
|
||||||
|
return null;
|
||||||
|
} else
|
||||||
|
return cmdOutput;
|
||||||
|
} catch (err) {
|
||||||
|
c.log("Server cannot generate a safe password.")
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"rootDir": "./",
|
||||||
|
"outDir": "output",
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution":"nodenext",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noImplicitThis": false,
|
||||||
|
"resolveJsonModule":true,
|
||||||
|
"strictNullChecks":false,
|
||||||
|
"noImplicitReturns":false,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user