definitely not done yet

This commit is contained in:
May 2024-05-01 03:23:12 -07:00
parent 5c3a324c64
commit 60b0308e31
7 changed files with 100 additions and 27 deletions

View file

@ -7,7 +7,7 @@ import type { Configuration } from "../config.js"
const EXPIRE_AFTER = 20 * 60 * 1000
const DISCORD_EPOCH = 1420070400000
// Converts a snowflake ID string into a JS Date object using the provided epoch (in ms), or Discord's epoch if not provided
function convertSnowflakeToDate(
export function convertSnowflakeToDate(
snowflake: string | number,
epoch = DISCORD_EPOCH
) {

View file

@ -2,12 +2,14 @@ import crypto from "crypto"
import * as auth from "./auth.js";
import { readFile, writeFile } from "fs/promises"
import { FileVisibility } from "./files.js";
import { AccountSchemas } from "./schemas/index.js";
import { z } from "zod"
// this is probably horrible
// but i don't even care anymore
export let Accounts: Account[] = []
/*
export interface Account {
id : string
username : string
@ -25,7 +27,9 @@ export interface Account {
color? : string
largeImage? : boolean
}
}
}*/
export type Account = z.infer<typeof AccountSchemas.Account>
/**
* @description Create a new account.
@ -45,7 +49,8 @@ export async function create(username:string,pwd:string,admin:boolean=false):Pro
password: password.hash(pwd),
files: [],
admin: admin,
defaultFileVisibility: "public"
defaultFileVisibility: "public",
settings: AccountSchemas.Settings.User.parse({})
}
)

View file

@ -2,7 +2,7 @@ import { readFile, writeFile } from "node:fs/promises"
import { Readable, Writable } from "node:stream"
import crypto from "node:crypto"
import { files } from "./accounts.js"
import { Client as API } from "./DiscordAPI/index.js"
import { Client as API, convertSnowflakeToDate } from "./DiscordAPI/index.js"
import type { APIAttachment } from "discord-api-types/v10"
import config, { Configuration } from "./config.js"
import "dotenv/config"
@ -624,28 +624,37 @@ export default class Files {
}
/**
* @description Update a file from monofile 1.2 to allow for range requests with Content-Length to that file.
* @description Update a file from monofile 1.x to 2.x
* @param uploadId Target file's ID
*/
async update(uploadId: string) {
let target_file = this.files[uploadId]
let attachment_sizes = []
let attachments: APIAttachment[] = []
for (let message of target_file.messageids) {
let attachments = (await this.api.fetchMessage(message)).attachments
for (let attachment of attachments) {
attachment_sizes.push(attachment.size)
attachments.push(attachment)
}
}
if (!target_file.sizeInBytes)
target_file.sizeInBytes = attachment_sizes.reduce(
(a, b) => a + b,
target_file.sizeInBytes = attachments.reduce(
(a, b) => a + b.size,
0
)
if (!target_file.chunkSize) target_file.chunkSize = attachment_sizes[0]
if (!target_file.chunkSize) target_file.chunkSize = attachments[0].size
if (!target_file.lastModified) target_file.lastModified = convertSnowflakeToDate(target_file.messageids[target_file.messageids.length-1]).getTime()
// this feels like needlessly heavy
// we should probably just do this in an actual readFile idk
if (!target_file.md5) {
let hash = crypto.createHash("md5");
(await this.readFileStream(uploadId)).pipe(hash).once("end", () => target_file.md5 = hash.digest("hex"))
}
}
/**

View file

@ -1,5 +1,5 @@
import {z} from "zod"
import { FileVisibility } from "./files.js"
import { FileId, FileVisibility } from "./files.js"
export const StringPassword = z.string().min(8,"password must be at least 8 characters")
export const Password =
@ -8,7 +8,52 @@ export const Password =
salt: z.string()
})
export const Username =
z.string().min(3, "username too short").max(20, "username too long").regex(/[A-Za-z0-9_\-\.]+/, "username contains invalid characters")
z.string()
.min(3, "username too short")
.max(20, "username too long")
.regex(/^[A-Za-z0-9_\-\.]+$/, "username contains invalid characters")
export namespace Settings {
export const Theme = z.discriminatedUnion("theme", [
z.object({
theme: z.literal("catppuccin"),
variant: z.enum(["latte","frappe","macchiato","mocha","adaptive"]),
accent: z.enum([
"rosewater",
"flamingo",
"pink",
"mauve",
"red",
"maroon",
"peach",
"yellow",
"green",
"teal",
"sky",
"sapphire",
"blue",
"lavender"
])
}),
z.object({
theme: z.literal("custom"),
id: FileId
})
])
export const BarSide = z.enum(["top","left","bottom","right"])
export const Interface = z.object({
theme: Theme.default({theme: "catppuccin", variant: "adaptive", accent: "sky"}),
barSide: BarSide.default("left")
})
export const Links = z.object({
color: z.string().toLowerCase().length(6).regex(/^[a-f0-9]+$/,"illegal characters").optional(),
largeImage: z.boolean().default(false)
})
export const User = z.object({
interface: Interface.default({}), links: Links.default({})
})
}
export const Account =
z.object({
id: z.string(),
@ -17,5 +62,7 @@ export const Account =
password: Password,
files: z.array(z.string()),
admin: z.boolean(),
defaultFileVisibility: FileVisibility
defaultFileVisibility: FileVisibility,
settings: Settings.User
})

View file

@ -2,7 +2,7 @@ import {z} from "zod"
import config from "../config.js"
export const FileId = z.string()
.regex(/[A-Za-z0-9_\-\.\!\=\:\&\$\,\+\;\@\~\*\(\)\']+/,"file ID uses invalid characters")
.regex(/^[A-Za-z0-9_\-\.\!\=\:\&\$\,\+\;\@\~\*\(\)\']+$/,"file ID uses invalid characters")
.max(config.maxUploadIdLength,"file ID too long")
.min(1, "you... *need* a file ID")
export const FileVisibility = z.enum(["public", "anonymous", "private"])

View file

@ -81,7 +81,7 @@ const validators: {
}
},
email: {
schema: AccountSchemas.Account.shape.email.optional(),
schema: AccountSchemas.Account.shape.email.nullable(),
validator: (actor, target, params, ctx) => {
if (
!params.currentPassword || // actor on purpose here to allow admins
@ -191,6 +191,23 @@ const validators: {
else return [400, "cannot demote an admin"]
}
},
settings: {
schema: AccountSchemas.Settings.User.partial(),
validator: (actor, target, params) => {
let base = AccountSchemas.Settings.User.default({}).parse(target.settings)
let visit = (bse: Record<string, any>, nw: Record<string, any>) => {
for (let [key,value] of Object.entries(nw)) {
if (typeof value == "object") visit(bse[key], value)
else bse[key] = value
}
}
visit(base, params.settings)
return AccountSchemas.Settings.User.parse(base) // so that toLowerCase is called again... yeah that's it
}
},
}
router.use(getAccount)
@ -246,8 +263,9 @@ export default function (files: Files) {
login(ctx, account)
return ctx.text("logged in")
})
.catch(() => {
return ServeError(ctx, 500, "internal server error")
.catch((e) => {
console.error(e)
return ServeError(ctx, 500, e instanceof z.ZodError ? issuesToMessage(e.issues) : "internal server error")
})
})
@ -354,11 +372,5 @@ export default function (files: Files) {
})
})
router.get("/:user/css", async (ctx) => {
let acc = ctx.get("account")
if (acc?.customCSS) return ctx.redirect(`/file/${acc.customCSS}`)
else return ctx.text("")
})
return router
}

View file

@ -75,18 +75,18 @@ export default function (files: Files) {
<meta property="og:video:height" content="720">`
: "")
: "") +
(fileOwner?.embed?.largeImage &&
(fileOwner?.settings?.links?.largeImage &&
file.visibility != "anonymous" &&
file.mime.startsWith("image/")
? `<meta name="twitter:card" content="summary_large_image">`
: "") +
`\n<meta name="theme-color" content="${
fileOwner?.embed?.color &&
fileOwner?.settings?.links.color &&
file.visibility != "anonymous" &&
(ctx.req.header("user-agent") || "").includes(
"Discordbot"
)
? `#${fileOwner.embed.color}`
? `#${fileOwner?.settings?.links.color}`
: "rgb(30, 33, 36)"
}">`
)