this code SUUUUCKs but i dont CARE

This commit is contained in:
May 2023-02-26 10:47:03 -08:00
parent 55e10f5408
commit b95a33e39d
50 changed files with 5456 additions and 5047 deletions

1
assets/icons/README.md Normal file
View file

@ -0,0 +1 @@
Icons are part of Microsoft's Fluent icons

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55c-.531 0-1.055-.076-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0 1.25 1.25 0 0 0 2.5 0Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 529 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-5.478 2A6.47 6.47 0 0 0 11 17.5c0 1.644.61 3.145 1.617 4.29-.802.141-1.675.21-2.617.21-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.305-2.843v-.907A2.25 2.25 0 0 1 4.254 14h7.768Zm3.071.966-.07.058-.057.07a.5.5 0 0 0 0 .568l.058.069 1.77 1.77-1.768 1.766-.057.07a.5.5 0 0 0 0 .568l.058.07.069.057a.5.5 0 0 0 .568 0l.07-.058 1.766-1.767 1.77 1.77.069.058a.5.5 0 0 0 .568 0l.07-.058.058-.07a.5.5 0 0 0 0-.568l-.058-.07-1.77-1.768 1.772-1.77.058-.07a.5.5 0 0 0 0-.568l-.058-.069-.069-.058a.5.5 0 0 0-.569 0l-.069.058-1.771 1.77-1.77-1.77-.07-.058a.5.5 0 0 0-.492-.043l-.076.043ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 791 B

1
assets/icons/logout.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.25 2.75a1.5 1.5 0 0 0-1.5 1.5v15.5a1.5 1.5 0 0 0 1.5 1.5h5.94a6.5 6.5 0 0 1 7.06-10.012V4.25a1.5 1.5 0 0 0-1.5-1.5H6.25Zm2.25 10.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Zm9 9.75a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Zm3.5-5.5a.5.5 0 0 1-.5.5h-4.793l1.647 1.646a.5.5 0 0 1-.708.708l-2.5-2.5a.5.5 0 0 1 0-.708l2.5-2.5a.5.5 0 0 1 .708.708L15.707 17H20.5a.5.5 0 0 1 .5.5Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View file

@ -3,5 +3,10 @@
"maxDiscordFileSize": 8388608, "maxDiscordFileSize": 8388608,
"targetGuild": "1024080490677936248", "targetGuild": "1024080490677936248",
"targetChannel": "1024080525993971913", "targetChannel": "1024080525993971913",
"requestTimeout":120000 "requestTimeout":120000,
"accounts": {
"registrationEnabled": true,
"requiredForUpload": true
}
} }

56
package-lock.json generated
View file

@ -14,6 +14,7 @@
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"axios": "^0.27.2", "axios": "^0.27.2",
"body-parser": "^1.20.0", "body-parser": "^1.20.0",
"cookie-parser": "^1.4.6",
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"express": "^4.18.1", "express": "^4.18.1",
@ -22,6 +23,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@types/cookie-parser": "^1.4.3",
"rollup": "^3.11.0", "rollup": "^3.11.0",
"rollup-plugin-svelte": "^7.1.0", "rollup-plugin-svelte": "^7.1.0",
"sass": "^1.57.1", "sass": "^1.57.1",
@ -187,6 +189,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/cookie-parser": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
@ -485,6 +496,26 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": { "node_modules/cookie-signature": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -1749,6 +1780,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/cookie-parser": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/estree": { "@types/estree": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
@ -1990,6 +2030,22 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
}, },
"cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"requires": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
}
}
},
"cookie-signature": { "cookie-signature": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",

View file

@ -19,6 +19,7 @@
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"axios": "^0.27.2", "axios": "^0.27.2",
"body-parser": "^1.20.0", "body-parser": "^1.20.0",
"cookie-parser": "^1.4.6",
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"express": "^4.18.1", "express": "^4.18.1",
@ -27,6 +28,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@types/cookie-parser": "^1.4.3",
"rollup": "^3.11.0", "rollup": "^3.11.0",
"rollup-plugin-svelte": "^7.1.0", "rollup-plugin-svelte": "^7.1.0",
"sass": "^1.57.1", "sass": "^1.57.1",

View file

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>

View file

@ -1,16 +1,17 @@
/*
i really should split this up into different modules
*/
import bodyParser from "body-parser" import bodyParser from "body-parser"
import multer, {memoryStorage} from "multer" import multer, {memoryStorage} from "multer"
import cookieParser from "cookie-parser";
import Discord, { IntentsBitField, Client } from "discord.js" import Discord, { IntentsBitField, Client } from "discord.js"
import express from "express" import express from "express"
import fs, { link } from "fs" import fs, { link } from "fs"
import axios, { AxiosResponse } from "axios" import axios, { AxiosResponse } from "axios"
import ServeError from "./lib/errors"
import ServeError from "./lib/errors"
import Files from "./lib/files" import Files from "./lib/files"
import * as auth from "./lib/auth"
import * as Accounts from "./lib/accounts"
import { authRoutes } from "./routes/authRoutes";
require("dotenv").config() require("dotenv").config()
const multerSetup = multer({storage:memoryStorage()}) const multerSetup = multer({storage:memoryStorage()})
@ -23,6 +24,9 @@ app.use("/static/style",express.static("out/style"))
app.use("/static/js",express.static("out/client")) app.use("/static/js",express.static("out/client"))
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]})) app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
app.use(cookieParser())
app.use("/auth",authRoutes)
// funcs // funcs
// init data // init data
@ -59,7 +63,13 @@ app.post("/upload",multerSetup.single('file'),async (req,res) => {
params = JSON.parse(prm) params = JSON.parse(prm)
} }
files.uploadFile({uploadId:params.uploadId,name:req.file.originalname,mime:req.file.mimetype},req.file.buffer) files.uploadFile({
owner: auth.validate(req.cookies.auth),
uploadId:params.uploadId,
name:req.file.originalname,
mime:req.file.mimetype
},req.file.buffer)
.then((uID) => res.send(uID)) .then((uID) => res.send(uID))
.catch((stat) => { .catch((stat) => {
res.status(stat.status); res.status(stat.status);
@ -83,12 +93,20 @@ app.post("/clone",(req,res) => {
res.send("[err] invalid url") res.send("[err] invalid url")
} }
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => { axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
files.uploadFile({
owner: auth.validate(req.cookies.auth),
name:j.url.split("/")[req.body.split("/").length-1] || "generic",
mime:data.headers["content-type"],
uploadId:j.uploadId
},Buffer.from(data.data))
.then((uID) => res.send(uID)) .then((uID) => res.send(uID))
.catch((stat) => { .catch((stat) => {
res.status(stat.status); res.status(stat.status);
res.send(`[err] ${stat.message}`) res.send(`[err] ${stat.message}`)
}) })
}).catch((err) => { }).catch((err) => {
console.log(err) console.log(err)
res.status(400) res.status(400)
@ -118,7 +136,15 @@ app.get("/download/:fileId",(req,res) => {
.replace(/\</g,"&lt;") .replace(/\</g,"&lt;")
.replace(/\>/g,"&gt;") .replace(/\>/g,"&gt;")
) )
.replace(/\$metaTags/g,file.mime.startsWith("image/") ? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />` : "") .replace(/\$metaTags/g,
file.mime.startsWith("image/")
? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />`
: (
file.mime.startsWith("video/")
? `<meta name="og:video:url" content="https://${req.headers.host}/file/${req.params.fileId}" />\n<meta name="og:video:type" content="${file.mime.replace(/\"/g,"")}">`
: ""
)
)
) )
}) })
} else { } else {

View file

@ -8,14 +8,15 @@ import { readFile, writeFile } from "fs/promises"
export let Accounts: Account[] = [] export let Accounts: Account[] = []
export interface Account { export interface Account {
id : string id : string
username: string username : string
password: { password : {
hash: string hash : string
salt: string salt : string
} }
accounts: string[] files : string[]
admin : boolean collections : string[]
admin : boolean
} }
export function create(username:string,pwd:string,admin:boolean=false) { export function create(username:string,pwd:string,admin:boolean=false) {
@ -26,7 +27,8 @@ export function create(username:string,pwd:string,admin:boolean=false) {
id: accId, id: accId,
username: username, username: username,
password: password.hash(pwd), password: password.hash(pwd),
accounts: [], files: [],
collections: [],
admin: admin admin: admin
} }
) )
@ -82,39 +84,6 @@ export namespace password {
} }
} }
export namespace rbxaccounts {
export function add(id:string,name:string) {
let acc = getFromId(id)
if (!acc) return
/* check for account that already has name */
let idx = acc.accounts.findIndex(e=>e==name)
if (idx > -1) return
acc.accounts = [...acc.accounts,name]
save()
return
}
export function remove(id:string,name:string) {
let acc = getFromId(id)
if (!acc) return
let idx = acc.accounts.findIndex(e=>e==name)
if (idx < 0) return
acc.accounts.splice(idx,1)
save()
return
}
export function clear(id:string) {
let acc = getFromId(id)
if (!acc) return
acc.accounts = []
save()
return
}
}
export function save() { export function save() {
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts)) writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
.catch((err) => console.error(err)) .catch((err) => console.error(err))

View file

@ -20,6 +20,7 @@ export default async function ServeError(
} }
// serve error // serve error
res.statusMessage = reason
res.status(code) res.status(code)
res.send( res.send(
errorPage errorPage

View file

@ -19,7 +19,8 @@ export function generateFileId() {
export interface FileUploadSettings { export interface FileUploadSettings {
name?: string, name?: string,
mime: string, mime: string,
uploadId?: string uploadId?: string,
owner?:string
} }
export interface Configuration { export interface Configuration {
@ -27,13 +28,19 @@ export interface Configuration {
maxDiscordFileSize: number, maxDiscordFileSize: number,
targetGuild: string, targetGuild: string,
targetChannel: string, targetChannel: string,
requestTimeout: number requestTimeout: number,
accounts: {
registrationEnabled: boolean,
requiredForUpload: boolean
}
} }
export interface FilePointer { export interface FilePointer {
filename:string, filename:string,
mime:string, mime:string,
messageids:string[] messageids:string[],
owner?:string
} }
export interface StatusCodeError { export interface StatusCodeError {
@ -86,6 +93,11 @@ export default class Files {
return return
} }
if (!settings.owner && this.config.accounts.requiredForUpload) {
reject({status:401,message:"an account is required for upload"});
return
}
let uploadId = (settings.uploadId || generateFileId()).toString(); let uploadId = (settings.uploadId || generateFileId()).toString();
if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) { if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) {
@ -159,7 +171,9 @@ export default class Files {
{ {
filename:settings.name, filename:settings.name,
messageids:msgIds, messageids:msgIds,
mime:settings.mime mime:settings.mime,
owner:settings.owner
} }
)) ))
}) })

View file

@ -0,0 +1,137 @@
import bodyParser from "body-parser";
import { Router } from "express";
import * as Accounts from "../lib/accounts";
import * as auth from "../lib/auth";
import ServeError from "../lib/errors";
let parser = bodyParser.json({
type: ["text/plain","application/json"]
})
export let authRoutes = Router();
let config = require(`${process.cwd()}/config.json`)
authRoutes.post("/login", parser, (req,res) => {
let body:{[key:string]:any}
try {
body = JSON.parse(req.body)
} catch {
ServeError(res,400,"bad request")
return
}
if (typeof body.username != "string" || typeof body.password != "string") {
ServeError(res,400,"please provide a username or password")
return
}
if (auth.validate(req.cookies.auth)) return
/*
check if account exists
*/
let acc = Accounts.getFromUsername(body.username)
if (!acc) {
ServeError(res,401,"username or password incorrect")
return
}
if (!Accounts.password.check(acc.id,body.password)) {
ServeError(res,401,"username or password incorrect")
return
}
/*
assign token
*/
res.cookie("auth",auth.create(acc.id,(3*24*60*60*1000)))
res.status(200)
res.end()
})
authRoutes.post("/create", parser, (req,res) => {
if (!config.accounts.registrationEnabled) {
ServeError(res,403,"account registration disabled")
return
}
let body:{[key:string]:any}
try {
body = JSON.parse(req.body)
} catch {
ServeError(res,400,"bad request")
return
}
if (auth.validate(req.cookies.auth)) return
if (typeof body.username != "string" || typeof body.password != "string") {
ServeError(res,400,"please provide a username or password")
return
}
/*
check if account exists
*/
let acc = Accounts.getFromUsername(body.username)
if (acc) {
ServeError(res,400,"account with this username already exists")
return
}
if (body.username.length < 3 || body.username.length > 20) {
ServeError(res,400,"username must be over or equal to 3 characters or under or equal to 20 characters in length")
return
}
if ((body.username.match(/[A-Za-z0-9_\-\.]+/) || [])[0] != body.username) {
ServeError(res,400,"username contains invalid characters")
return
}
if (body.password.length < 8) {
ServeError(res,400,"password must be 8 characters or longer")
return
}
let newAcc = Accounts.create(body.username,body.password)
/*
assign token
*/
res.cookie("auth",auth.create(newAcc,(3*24*60*60*1000)))
res.status(200)
res.end()
})
authRoutes.post("/logout", (req,res) => {
if (!auth.validate(req.cookies.auth)) {
ServeError(res, 401, "not logged in")
return
}
auth.invalidate(req.cookies.auth)
res.send("logged out")
})
authRoutes.get("/me", (req,res) => {
if (!auth.validate(req.cookies.auth)) {
ServeError(res, 401, "not logged in")
return
}
// lazy rn so
let acc = Accounts.getFromToken(req.cookies.auth)
res.send(acc)
})

View file

@ -69,6 +69,25 @@
} }
} }
.pwError {
div {
border:none;
border-radius:0;
width:100%;
padding:5px;
background-color:#663333;
color:#dddddd;
outline:none;
font-size:14px;
text-align:left;
@media screen and (max-width: 500px) {
font-size:16px;
padding:10px;
}
}
}
.lgBtnContainer { .lgBtnContainer {
display:flex; display:flex;
position:relative; position:relative;
@ -117,4 +136,53 @@
} }
} }
.loggedIn {
position:absolute;
left:10px;
top:10px;
width:calc( 100% - 20px );
height:calc( 100% - 20px );
h1 {
font-weight:600;
font-size:20px;
color: #AAAAAA;
}
.accountOptions {
button {
position:relative;
width:100%;
cursor:pointer;
height:50px;
background-color: #191919;
border:none;
border-bottom:1px solid #AAAAAA;
transition-duration:150ms;
img {
position:absolute;
left:13px;
top:50%;
transform:translateY(-50%);
}
p {
position:absolute;
top:50%;
left:50px;
color:#DDDDDD;
transform:translateY(-50%);
font-size:14px;
}
&:hover {
transition-duration:150ms;
background-color: #252525;
}
}
}
}
} }

View file

@ -2,6 +2,7 @@
import { circOut } from "svelte/easing"; import { circOut } from "svelte/easing";
import { scale } from "svelte/transition"; import { scale } from "svelte/transition";
import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte"; import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte";
import { account } from "./stores.mjs";
import { _void } from "./transition/_void"; import { _void } from "./transition/_void";
/** /**
@ -22,7 +23,7 @@
<!-- too lazy to make this better --> <!-- too lazy to make this better -->
<button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button> <button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button>
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>account</button> <button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>{$account.username ? `@${$account.username}` : "account"}</button>
<button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button> <button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
<div /> <!-- not sure what's offcenter but something is <div /> <!-- not sure what's offcenter but something is

View file

@ -3,19 +3,12 @@
import { padding_scaleY } from "./transition/padding_scaleY.js" import { padding_scaleY } from "./transition/padding_scaleY.js"
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { circIn, circOut } from "svelte/easing"; import { circIn, circOut } from "svelte/easing";
import { serverStats, refresh_stats, account } from "./stores.mjs";
import AttachmentZone from "./uploader/AttachmentZone.svelte"; import AttachmentZone from "./uploader/AttachmentZone.svelte";
// stats // stats
let ServerStats = {}
let refresh_stats = () => {
fetch("/server").then(async (data) => {
ServerStats = await data.json()
})
}
refresh_stats() refresh_stats()
// uploads // uploads
@ -118,7 +111,7 @@
let eased = circOut(t) let eased = circOut(t)
return ` return `
height: ${eased*(node.offsetHeight-20)}px; height: ${eased*(node.offsetHeight-22)}px;
padding: ${eased*10}px 10px; padding: ${eased*10}px 10px;
` `
} }
@ -130,7 +123,7 @@
<div id="uploadWindow"> <div id="uploadWindow">
<h1>monofile</h1> <h1>monofile</h1>
<p style:color="#999999"> <p style:color="#999999">
<span class="number">{ServerStats.version ? `v${ServerStats.version}` : "•••"}</span>&nbsp;&nbsp—&nbsp;&nbsp;Discord based file sharing <span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span>&nbsp;&nbsp—&nbsp;&nbsp;Discord based file sharing
</p> </p>
<div style:min-height="10px" /> <div style:min-height="10px" />
@ -195,18 +188,30 @@
</div> </div>
{#if uploadInProgress == false} {#if uploadInProgress == false}
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
<div style:min-height="10px" transition:padding_scaleY /> <!-- if required for upload, check if logged in -->
{#if Object.keys(uploads).length > 0} {#if ($serverStats.accounts||{}).requiredForUpload ? !!$account.username : true}
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
<div transition:_void style:min-height="10px" /> <AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} />
{#if Object.keys(uploads).length > 0}
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
{/if}
{:else}
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p>
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
{/if} {/if}
{/if} {/if}
<p style:color="#999999" style:text-align="center"> <p style:color="#999999" style:text-align="center">
Hosting <span class="number" style:font-weight="600">{ServerStats.files || "•••"}</span> files Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files
Maximum filesize is <span class="number" style:font-weight="600">{((ServerStats.maxDiscordFileSize || 0)*(ServerStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span> Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span>
<br /> <br />
</p> </p>

View file

@ -2,32 +2,132 @@
import Pulldown from "./Pulldown.svelte" import Pulldown from "./Pulldown.svelte"
import { padding_scaleY } from "../transition/padding_scaleY" import { padding_scaleY } from "../transition/padding_scaleY"
import { circIn,circOut } from "svelte/easing" import { circIn,circOut } from "svelte/easing"
import { account, fetchAccountData, serverStats } from "../stores.mjs";
import { fade } from "svelte/transition";
let targetAction let targetAction
let inProgress
let authError
let pwErr
// lazy
let username
let password
let execute = () => {
if (inProgress) return
inProgress = true
fetch(`/auth/${targetAction}`, {
method: "POST",
body: JSON.stringify({
username, password
})
}).then(async (res) => {
inProgress = false
if (res.status != 200) {
authError = await res.json().catch(() => {
return {
status: res.status,
message: res.statusText
}
})
}
fetchAccountData();
}).catch(() => {})
}
$: {
if (pwErr && authError) {
pwErr.animate({
backgroundColor: ["#885555","#663333"],
easing: "ease-out"
},650)
}
}
// actual account menu
</script> </script>
<Pulldown name="accounts"> <Pulldown name="accounts">
<div class="notLoggedIn"> {#if Object.keys($account).length == 0}
<div class="container_div">
<h1>monofile <span style:color="#999999">accounts</span></h1>
<p class="flavor">Gain control of your uploads.</p>
{#if targetAction} <div class="notLoggedIn" transition:fade={{duration:200}}>
<div class="container_div">
<h1>monofile <span style:color="#999999">accounts</span></h1>
<p class="flavor">Gain control of your uploads.</p>
<div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local> {#if targetAction}
<input placeholder="username" type="text">
<input placeholder="password" type="password">
<button>{targetAction=="login" ? "Log in" : "Create account"}</button>
</div>
{:else} <div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
{#if !$serverStats.accounts.registrationEnabled && targetAction == "create"}
<div class="pwError">
<div style:background-color="#554C33">
<p>Account registration has been disabled by this instance's owner</p>
</div>
</div>
{/if}
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local> {#if authError}
<button on:click={() => targetAction="login"}>Log in</button> <div class="pwError" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
<button on:click={() => targetAction="create"}>Sign up</button> <div bind:this={pwErr}>
</div> <p><strong>{authError.status}</strong> {authError.message}</p>
</div>
</div>
{/if}
{/if} <input placeholder="username" type="text" bind:value={username}>
<input placeholder="password" type="password" bind:value={password}>
<button on:click={execute}>{ inProgress ? "• • •" : (targetAction=="login" ? "Log in" : "Create account") }</button>
</div>
{:else}
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
<button on:click={() => targetAction="login"}>Log in</button>
<button on:click={() => targetAction="create"}>Sign up</button>
</div>
{/if}
</div>
</div> </div>
</div>
{:else}
<div class="loggedIn" transition:fade={{duration:200}}>
<h1>
Hey there, <span class="monospace" style:font-size="18px">@{$account.username}</span>
</h1>
<div style:min-height="10px" style:border-bottom="1px solid #AAAAAA" />
<div class="accountOptions">
<button>
<img src="/static/assets/icons/change_password.svg" alt="change password">
<p>Change password</p>
</button>
<button>
<img src="/static/assets/icons/delete_account.svg" alt="delete account">
<p>Delete account</p>
</button>
<button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}>
<img src="/static/assets/icons/logout.svg" alt="logout">
<p>Log out</p>
</button>
</div>
</div>
{/if}
</Pulldown> </Pulldown>

View file

@ -1,3 +1,23 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
export let pulldownManager = writable(0) export let pulldownManager = writable(0)
export let account = writable({})
export let serverStats = writable({})
export let fetchAccountData = function() {
fetch("/auth/me").then(async (response) => {
if (response.status == 200) {
account.set(await response.json())
} else {
account.set({})
}
}).catch((err) => { console.error(err) })
}
export let refresh_stats = () => {
fetch("/server").then(async (data) => {
serverStats.set(await data.json())
}).catch((err) => { console.error(err) })
}
fetchAccountData()

View file

@ -1,6 +1,6 @@
import { circIn, circOut } from "svelte/easing" import { circIn, circOut } from "svelte/easing"
export function _void(node, { duration, easingFunc, op, prop }) { export function _void(node, { duration, easingFunc, op, prop, rTarg }) {
let rect = node.getBoundingClientRect() let rect = node.getBoundingClientRect()
return { return {
@ -10,7 +10,7 @@ export function _void(node, { duration, easingFunc, op, prop }) {
return ` return `
white-space: nowrap; white-space: nowrap;
${prop||"height"}: ${(eased)*(rect[prop||"height"])}px; ${prop||"height"}: ${(eased)*(rect[rTarg||prop||"height"])}px;
padding: 0px; padding: 0px;
opacity:${eased}; opacity:${eased};
overflow: clip; overflow: clip;