From 98ac93aad8fe1c503000acf4625a799b9e258e8f Mon Sep 17 00:00:00 2001 From: mc <56406996+McMistrzYT@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:28:46 +0100 Subject: [PATCH 01/13] update auth message --- Server/Source/Modules/Middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/Source/Modules/Middleware.ts b/Server/Source/Modules/Middleware.ts index dcb5fd3..89a3b5d 100644 --- a/Server/Source/Modules/Middleware.ts +++ b/Server/Source/Modules/Middleware.ts @@ -16,7 +16,7 @@ declare global { export function RequireAuthentication(Relations?: object) { return async (req: Request, res: Response, next: NextFunction) => { if (!req.header("X-Partypack-Token") && !req.cookies["Token"] && !req.header("Authorization")) - return res.status(401).send("This endpoint requires authorization."); + return res.status(401).send("You must be logged in to perform this action."); let JWT: JwtPayload; try { @@ -55,4 +55,4 @@ export function ValidateQuery(Schema: j.Schema) { res.status(400).json(err) } } -} \ No newline at end of file +} From 03106e9da78dd99e3e03878f642ec8d200a171e2 Mon Sep 17 00:00:00 2001 From: mc <56406996+McMistrzYT@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:28:02 +0100 Subject: [PATCH 02/13] update no drafts text to specify that you dont have drafts, not bookmarks --- src/routes/Profile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Profile.tsx b/src/routes/Profile.tsx index a2ac633..e8c061e 100644 --- a/src/routes/Profile.tsx +++ b/src/routes/Profile.tsx @@ -270,7 +270,7 @@ export function Profile() { }}>Delete draft ; }) - : You have no bookmarked songs. + : You have no drafts. } : From 58c8eb49b62d82b956bf03f196f8a976bf38c870 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Mon, 5 Feb 2024 20:52:39 +0000 Subject: [PATCH 03/13] Add previews (mostly) --- Server/Source/Handlers/Database.ts | 18 +++- Server/Source/Modules/FNUtil.ts | 2 +- Server/Source/Routes/Downloads.ts | 24 +++-- Server/Source/Routes/Drafting.ts | 88 ++++++++++++++--- Server/Source/Schemas/Song.ts | 9 +- Server/package-lock.json | 152 ++++++++++++++++++++++++++++- Server/package.json | 1 + 7 files changed, 270 insertions(+), 24 deletions(-) diff --git a/Server/Source/Handlers/Database.ts b/Server/Source/Handlers/Database.ts index 9e7e5de..beedbf3 100644 --- a/Server/Source/Handlers/Database.ts +++ b/Server/Source/Handlers/Database.ts @@ -1,10 +1,11 @@ -import { DataSource } from "typeorm"; +import { DataSource, IsNull } from "typeorm"; import { ENVIRONMENT, SAVED_DATA_PATH } from "../Modules/Constants"; import { Song } from "../Schemas/Song"; import { ForcedCategory } from "../Schemas/ForcedCategory"; import { User } from "../Schemas/User"; import { Rating } from "../Schemas/Rating"; import { DiscordRole } from "../Schemas/DiscordRole"; +import { Debug } from "../Modules/Logger"; export const DBSource = new DataSource({ type: "better-sqlite3", @@ -26,6 +27,21 @@ export const DBSource = new DataSource({ (async () => { await DBSource.initialize(); + + // Look for songs without a PID here so we can resolve problems before we do anything else + const SongsWithNoPID = await Song.find({ where: { PID: IsNull() } }); + Debug(`We have ${SongsWithNoPID.length} song${SongsWithNoPID.length != 1 ? "s" : ""} with no PID`); + + SongsWithNoPID.forEach(async (Song) => { + Debug(`Fixing up ${Song.Name} PID`); + + // Existing songs that actually need separate PIDs (> 2 channels) will need to have their audio reuploaded entirely + // This is faster than checking to see if they all actually need one though... + Song.PID = Song.ID; + + await Song.save(); + Debug(`${Song.Name} PID is now ${Song.PID} to match ${Song.ID}`); + }) })(); /* diff --git a/Server/Source/Modules/FNUtil.ts b/Server/Source/Modules/FNUtil.ts index a971fac..a34c58f 100644 --- a/Server/Source/Modules/FNUtil.ts +++ b/Server/Source/Modules/FNUtil.ts @@ -111,7 +111,7 @@ export async function GenerateFortnitePages(ForUser: User | null): Promise<{ Suc siv: "Vocals", // siv - Vocals ID to use (only Vocals possible) qi: JSON.stringify({ // qi - Query Information (frontend related display stuff and language vocals channel related stuff) sid: Song.ID, // sid - Song UUID - pid: Song.ID, // pid - Playlist Asset ID + pid: Song.PID, // pid - Playlist Asset ID title: OriginalTrack._title, // title - Song Name - same as _title tracks: [ { diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index edd8df3..f4770a2 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -14,9 +14,12 @@ App.get("/song/download/:InternalID/:File", RequireAuthentication(), async (req, res) => { //const Song = AvailableFestivalSongs.find(x => x.UUID === req.params.SongUUID); - const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); + const SongData = await Song.findOne({ where: [ { ID: req.params.InternalID}, { PID: req.params.InternalID } ], relations: { Author: true } }); if (!SongData) return res.status(404).send("Song not found."); + + const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID; + const ManifestPath = `${SongData.Directory}/${IsPreview ? `PreviewManifest.mpd` : `Manifest.mpd`}`; if (SongData.IsDraft && (req.user!.PermissionLevel! < UserPermissions.VerifiedUser && SongData.Author.ID !== req.user!.ID)) return res.status(403).send("You cannot use this track, because it's a draft."); @@ -32,7 +35,7 @@ async (req, res) => { type: "main", language: "en", url: `${BaseURL}master.blurl`, - data: readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL) + data: readFileSync(ManifestPath).toString().replaceAll("{BASEURL}", BaseURL) } ], type: "vod", @@ -42,7 +45,7 @@ async (req, res) => { case "manifest": case "manifest.mpd": - return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL))); + return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(ManifestPath).toString().replaceAll("{BASEURL}", BaseURL))); case "cover": case "cover.png": @@ -65,27 +68,32 @@ async (req, res) => { if (!req.params.File.endsWith(".m4s") && !req.params.File.endsWith(".webm")) return res.sendStatus(403); - if (!existsSync(`${SongData.Directory}/Chunks/${req.params.File}`)) + const ChunkPath = `${SongData.Directory}/${IsPreview ? `PreviewChunks` : `Chunks`}/${req.params.File}` + if (!existsSync(ChunkPath)) return res.sendStatus(404); res.set("content-type", "video/mp4") - res.send(readFileSync(`${SongData.Directory}/Chunks/${req.params.File}`)); + res.send(readFileSync(ChunkPath)); + console.log(`Sending back ${ChunkPath}`); }); App.get("/:InternalID", RequireAuthentication(), async (req, res, next) => { - const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); + const SongData = await Song.findOne({ where: [ { ID: req.params.InternalID }, { PID: req.params.InternalID } ], relations: { Author: true } }); if (!SongData) return next(); // trust me bro + const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID; + console.log(`We have ${IsPreview ? `PREVIEW` : `MAIN`} request`); + if (SongData.IsDraft && ((req.user ? req.user.PermissionLevel < UserPermissions.VerifiedUser : true) && SongData.Author.ID !== req.user!.ID)) return res.status(403).send("You cannot use this track, because it's a draft."); - const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`; + const BaseURL = `${FULL_SERVER_ROOT}/song/download/${IsPreview ? SongData.PID : SongData.ID}/`; res.set("content-type", "application/json"); res.json({ - playlist: Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)).toString("base64"), + playlist: Buffer.from(readFileSync(`${SongData.Directory}/${IsPreview ? `PreviewManifest.mpd` : `Manifest.mpd`}`).toString().replaceAll("{BASEURL}", BaseURL)).toString("base64"), playlistType: "application/dash+xml", metadata: { assetId: "", diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index fc97666..6f0e44c 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -8,7 +8,8 @@ import { Song, SongStatus } from "../Schemas/Song"; import { Debug } from "../Modules/Logger"; import { magenta } from "colorette"; import { fromBuffer } from "file-type"; -import { rmSync, writeFileSync, renameSync, readFileSync } from "fs"; +import { v4 } from "uuid"; +import { rmSync, writeFileSync, renameSync, readFileSync, existsSync, mkdirSync } from "fs"; import { FULL_SERVER_ROOT, MAX_AMOUNT_OF_DRAFTS_AT_ONCE, SAVED_DATA_PATH } from "../Modules/Constants"; import { UserPermissions } from "../Schemas/User"; @@ -193,34 +194,97 @@ App.post("/upload/audio", await SongData.save(); } - await writeFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`, Decoded); + const AudioPath = `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}`; + + await writeFileSync(AudioPath + `/Audio.${ext}`, Decoded); ffmpeg() - .input(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`) + .input(AudioPath + `/Audio.${ext}`) .audioCodec("libopus") .outputOptions([ "-use_timeline 1", "-f dash", "-mapping_family 255" ]) - .output(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`) + .output(AudioPath + `/Chunks/Manifest.mpd`) .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) .on("end", async () => { Debug("Ffmpeg finished running"); - rmSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`); - renameSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`, `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`); - // i love creating thread-safe code that always works! (never gonna error trust me) - writeFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`, readFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + // Check channels + ffmpeg.ffprobe(AudioPath + `/Audio.${ext}`, async (err, metadata) => { + if (err) // FUCK + { + console.log(err); + } + else if (metadata && metadata.streams && metadata.streams[0].codec_type == 'audio' && metadata.streams[0].channels && metadata.streams[0].channels > 2) // jfc ts + { + Debug(`Creating preview stream as it's needed...`); + + // Oh shit!! we need a preview stream!! so let's make one. - await SongData.reload(); - SongData.HasAudio = true; - await SongData.save(); + // Make a dir for it first + if (!existsSync(AudioPath + "/PreviewChunks")) + mkdirSync(AudioPath + "/PreviewChunks", { recursive: true }); + + // Then, figure out which channels from the original file to put into each channel on the output file. + // We already ran ffprobe earlier so we can just reuse that lol + var FilterLeft = "pan=stereo|c0="; + var FilterRight = "|c1="; + for (var i = 0; i < metadata.streams[0].channels; i++) + { + if (i == 0 || i % 2 == 0) + FilterLeft += `${i == 0 ? "" : "+"}c${i}` // out for 0 = "c0", out for 2 = "+c2" + else + FilterRight += `${i == 1 ? "" : "+"}c${i}` // out for 1 = "c1", out for 3 = "+c3" + } + + // Need to wait for this before removing the source file, but since it won't ALWAYS get called we don't put the rmSync in here + await new Promise((resolve, reject) => { + ffmpeg() + .input(AudioPath + `/Audio.${ext}`) + .audioCodec("libopus") + .audioFilter(FilterLeft + FilterRight) + .outputOptions([ + "-use_timeline 1", + "-f dash", + "-ac 2", // downmix + ]) + .output(AudioPath + `/PreviewChunks/PreviewManifest.mpd`) + .on("start", cl => Debug(`Creating preview stream with ${magenta(cl)}`)) + .on("end", async () => { + Debug(`Preview stream created`); + // Move the mpd out + renameSync(AudioPath + `/PreviewChunks/PreviewManifest.mpd`, AudioPath + `/PreviewManifest.mpd`); + writeFileSync(AudioPath + `/PreviewManifest.mpd`, readFileSync(AudioPath + `/PreviewManifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + SongData.PID = v4(); + await SongData.save(); + resolve(); + }) + .on("error", async (e, stdout, stderr) => { + console.error(e); + console.log(stdout); + console.error(stderr); + reject(e); + }).run(); + }); + } + + rmSync(AudioPath + `/Audio.${ext}`); + + renameSync(AudioPath + `/Chunks/Manifest.mpd`, AudioPath + `/Manifest.mpd`); + // i love creating thread-safe code that always works! (never gonna error trust me) + writeFileSync(AudioPath + `/Manifest.mpd`, readFileSync(AudioPath + `/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + + await SongData.reload(); + SongData.HasAudio = true; + await SongData.save(); + }); }) .on("error", async (e, stdout, stderr) => { console.error(e); console.log(stdout); console.error(stderr); - rmSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`); + rmSync(AudioPath + `/Audio.${ext}`); await SongData.reload(); SongData.Status = SongStatus.BROKEN; diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index 8e0f34b..3416c47 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -1,4 +1,4 @@ -import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, IsNull } from "typeorm"; import { FULL_SERVER_ROOT, SAVED_DATA_PATH } from "../Modules/Constants"; import { Rating } from "./Rating"; import { existsSync, mkdirSync, rmSync } from "fs"; @@ -21,6 +21,9 @@ export class Song extends BaseEntity { @PrimaryGeneratedColumn("uuid") ID: string; + @Column("uuid", { nullable: true, default: null }) + PID: string; + @ManyToOne(() => User, U => U.CreatedTracks) Author: User; @@ -102,6 +105,10 @@ export class Song extends BaseEntity { @BeforeInsert() Setup() { this.ID = v4(); + + if (this.PID == undefined) // im lazy but this will work regardless + this.PID = this.ID; // By default they should be the same to save space, if we *need* a preview stream later we can change this when processing audio + this.Directory = `${SAVED_DATA_PATH}/Songs/${this.ID}`; if (!existsSync(join(this.Directory, "Chunks"))) mkdirSync(join(this.Directory, "Chunks"), { recursive: true }); diff --git a/Server/package-lock.json b/Server/package-lock.json index f063e0d..452203d 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -23,6 +23,7 @@ "joi": "^17.12.0", "jsonwebtoken": "^9.0.2", "node-cron": "^3.0.3", + "ts-node": "^10.9.2", "typeorm": "^0.3.19", "underscore": "^1.13.6", "uuid": "^9.0.1" @@ -40,6 +41,17 @@ "typescript": "^5.2.2" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@discordjs/builders": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", @@ -175,6 +187,28 @@ "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -242,6 +276,26 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, "node_modules/@types/body-parser": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", @@ -420,6 +474,25 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -458,6 +531,11 @@ "node": ">= 6.0.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -901,6 +979,11 @@ "node": ">= 0.10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -974,6 +1057,14 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/discord-api-types": { "version": "0.37.61", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", @@ -1549,6 +1640,11 @@ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.8.0.tgz", "integrity": "sha512-lyWpfvNGVb5lu8YUAbER0+UMBTdR63w2mcSUlhhBTyVbxJvjgqwyAf3AZD6MprgK0uHuBoWXSDAMWLupX83o3Q==" }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2368,6 +2464,48 @@ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -2526,7 +2664,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2589,6 +2726,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2791,6 +2933,14 @@ "engines": { "node": ">=8" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } } } } diff --git a/Server/package.json b/Server/package.json index d92f4bd..df325db 100644 --- a/Server/package.json +++ b/Server/package.json @@ -55,6 +55,7 @@ "joi": "^17.12.0", "jsonwebtoken": "^9.0.2", "node-cron": "^3.0.3", + "ts-node": "^10.9.2", "typeorm": "^0.3.19", "underscore": "^1.13.6", "uuid": "^9.0.1" From a74e03cde9966a7944bab45ca6e08744a3d062f9 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Mon, 5 Feb 2024 20:57:02 +0000 Subject: [PATCH 04/13] Remove unused import --- Server/Source/Schemas/Song.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index 3416c47..22d1f63 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -1,4 +1,4 @@ -import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, IsNull } from "typeorm"; +import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { FULL_SERVER_ROOT, SAVED_DATA_PATH } from "../Modules/Constants"; import { Rating } from "./Rating"; import { existsSync, mkdirSync, rmSync } from "fs"; From 63c1706594e9a1a951a7a82f53fcdae7aec26102 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Tue, 6 Feb 2024 16:39:07 +0000 Subject: [PATCH 05/13] Remove extra logs --- Server/Source/Routes/Downloads.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index f4770a2..7fc566f 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -74,7 +74,6 @@ async (req, res) => { res.set("content-type", "video/mp4") res.send(readFileSync(ChunkPath)); - console.log(`Sending back ${ChunkPath}`); }); App.get("/:InternalID", @@ -85,7 +84,6 @@ async (req, res, next) => { return next(); // trust me bro const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID; - console.log(`We have ${IsPreview ? `PREVIEW` : `MAIN`} request`); if (SongData.IsDraft && ((req.user ? req.user.PermissionLevel < UserPermissions.VerifiedUser : true) && SongData.Author.ID !== req.user!.ID)) return res.status(403).send("You cannot use this track, because it's a draft."); From bb1bb3db16f70d474769c61fd42c674a637e9ad8 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Tue, 6 Feb 2024 18:35:44 +0000 Subject: [PATCH 06/13] Fix audio updating --- Server/Source/Routes/Drafting.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index 6f0e44c..499c24c 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -183,17 +183,22 @@ App.post("/upload/audio", if (req.user!.PermissionLevel! < UserPermissions.Administrator && SongData.Author.ID !== req.user!.ID) return res.status(403).send("You don't have permission to upload to this song."); + const ChunksPath = `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks`; + if (SongData.HasAudio) { if (SongData.Status !== SongStatus.BROKEN && SongData.Status !== SongStatus.DEFAULT && SongData.Status !== SongStatus.DENIED && SongData.Status !== SongStatus.PUBLIC) return res.status(400).send("You cannot update this song at this moment."); - rmSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks`, { recursive: true }); + rmSync(ChunksPath, { recursive: true }); SongData.HasAudio = false; SongData.IsDraft = true; SongData.Status = SongStatus.PROCESSING; await SongData.save(); } + if (!existsSync(ChunksPath)) + mkdirSync(ChunksPath); + const AudioPath = `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}`; await writeFileSync(AudioPath + `/Audio.${ext}`, Decoded); From 7a95c7a537f5cf79f95b66e9310df71538c1140b Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Tue, 6 Feb 2024 21:25:54 +0000 Subject: [PATCH 07/13] Update Drafting.ts c# dont fry my brain challenge --- Server/Source/Routes/Drafting.ts | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index 499c24c..d876f5e 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -201,35 +201,34 @@ App.post("/upload/audio", const AudioPath = `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}`; - await writeFileSync(AudioPath + `/Audio.${ext}`, Decoded); + await writeFileSync(`${AudioPath}/Audio.${ext}`, Decoded); ffmpeg() - .input(AudioPath + `/Audio.${ext}`) + .input(`${AudioPath}/Audio.${ext}`) .audioCodec("libopus") .outputOptions([ "-use_timeline 1", "-f dash", "-mapping_family 255" ]) - .output(AudioPath + `/Chunks/Manifest.mpd`) + .output(`${AudioPath}/Chunks/Manifest.mpd`) .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) .on("end", async () => { Debug("Ffmpeg finished running"); // Check channels - ffmpeg.ffprobe(AudioPath + `/Audio.${ext}`, async (err, metadata) => { + ffmpeg.ffprobe(`${AudioPath}/Audio.${ext}`, async (err, metadata) => { if (err) // FUCK - { - console.log(err); - } - else if (metadata && metadata.streams && metadata.streams[0].codec_type == 'audio' && metadata.streams[0].channels && metadata.streams[0].channels > 2) // jfc ts + return console.log(err); + + if (metadata && metadata.streams && metadata.streams[0].codec_type == 'audio' && metadata.streams[0].channels && metadata.streams[0].channels > 2) // jfc ts { Debug(`Creating preview stream as it's needed...`); // Oh shit!! we need a preview stream!! so let's make one. // Make a dir for it first - if (!existsSync(AudioPath + "/PreviewChunks")) - mkdirSync(AudioPath + "/PreviewChunks", { recursive: true }); + if (!existsSync(`${AudioPath}/PreviewChunks`)) + mkdirSync(`${AudioPath}/PreviewChunks`, { recursive: true }); // Then, figure out which channels from the original file to put into each channel on the output file. // We already ran ffprobe earlier so we can just reuse that lol @@ -246,7 +245,7 @@ App.post("/upload/audio", // Need to wait for this before removing the source file, but since it won't ALWAYS get called we don't put the rmSync in here await new Promise((resolve, reject) => { ffmpeg() - .input(AudioPath + `/Audio.${ext}`) + .input(`${AudioPath}/Audio.${ext}`) .audioCodec("libopus") .audioFilter(FilterLeft + FilterRight) .outputOptions([ @@ -254,13 +253,13 @@ App.post("/upload/audio", "-f dash", "-ac 2", // downmix ]) - .output(AudioPath + `/PreviewChunks/PreviewManifest.mpd`) + .output(`${AudioPath}/PreviewChunks/PreviewManifest.mpd`) .on("start", cl => Debug(`Creating preview stream with ${magenta(cl)}`)) .on("end", async () => { Debug(`Preview stream created`); // Move the mpd out - renameSync(AudioPath + `/PreviewChunks/PreviewManifest.mpd`, AudioPath + `/PreviewManifest.mpd`); - writeFileSync(AudioPath + `/PreviewManifest.mpd`, readFileSync(AudioPath + `/PreviewManifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + renameSync(`${AudioPath}/PreviewChunks/PreviewManifest.mpd`, `${AudioPath}/PreviewManifest.mpd`); + writeFileSync(`${AudioPath}/PreviewManifest.mpd`, readFileSync(`${AudioPath}/PreviewManifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); SongData.PID = v4(); await SongData.save(); resolve(); @@ -274,11 +273,11 @@ App.post("/upload/audio", }); } - rmSync(AudioPath + `/Audio.${ext}`); + rmSync(`${AudioPath}/Audio.${ext}`); - renameSync(AudioPath + `/Chunks/Manifest.mpd`, AudioPath + `/Manifest.mpd`); + renameSync(`${AudioPath}/Chunks/Manifest.mpd`, `${AudioPath}/Manifest.mpd`); // i love creating thread-safe code that always works! (never gonna error trust me) - writeFileSync(AudioPath + `/Manifest.mpd`, readFileSync(AudioPath + `/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + writeFileSync(`${AudioPath}/Manifest.mpd`, readFileSync(`${AudioPath}/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); await SongData.reload(); SongData.HasAudio = true; @@ -289,7 +288,7 @@ App.post("/upload/audio", console.error(e); console.log(stdout); console.error(stderr); - rmSync(AudioPath + `/Audio.${ext}`); + rmSync(`${AudioPath}/Audio.${ext}`); await SongData.reload(); SongData.Status = SongStatus.BROKEN; From 1cd0d4fc79ee1dfcbfc91c4dc9b22afff085d611 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Tue, 6 Feb 2024 21:40:56 +0000 Subject: [PATCH 08/13] Some requested changes --- Server/Source/Routes/Drafting.ts | 10 ++++++---- Server/Source/Schemas/Song.ts | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index d876f5e..f7ccb8e 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -220,9 +220,9 @@ App.post("/upload/audio", if (err) // FUCK return console.log(err); - if (metadata && metadata.streams && metadata.streams[0].codec_type == 'audio' && metadata.streams[0].channels && metadata.streams[0].channels > 2) // jfc ts + if (metadata.streams[0].codec_type == "audio" && metadata.streams[0].channels! > 2) { - Debug(`Creating preview stream as it's needed...`); + Debug("Creating preview stream as it's needed..."); // Oh shit!! we need a preview stream!! so let's make one. @@ -232,9 +232,11 @@ App.post("/upload/audio", // Then, figure out which channels from the original file to put into each channel on the output file. // We already ran ffprobe earlier so we can just reuse that lol + + // Output with 10 channels is "pan=stereo|c0=c0+c2+c4+c6+c8|c1=c1+c3+c5+c7+c9", ffmpeg uses this to decide how to downmix var FilterLeft = "pan=stereo|c0="; var FilterRight = "|c1="; - for (var i = 0; i < metadata.streams[0].channels; i++) + for (var i = 0; i < metadata.streams[0].channels!; i++) { if (i == 0 || i % 2 == 0) FilterLeft += `${i == 0 ? "" : "+"}c${i}` // out for 0 = "c0", out for 2 = "+c2" @@ -256,7 +258,7 @@ App.post("/upload/audio", .output(`${AudioPath}/PreviewChunks/PreviewManifest.mpd`) .on("start", cl => Debug(`Creating preview stream with ${magenta(cl)}`)) .on("end", async () => { - Debug(`Preview stream created`); + Debug("Preview stream created"); // Move the mpd out renameSync(`${AudioPath}/PreviewChunks/PreviewManifest.mpd`, `${AudioPath}/PreviewManifest.mpd`); writeFileSync(`${AudioPath}/PreviewManifest.mpd`, readFileSync(`${AudioPath}/PreviewManifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index 22d1f63..092bf25 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -21,8 +21,8 @@ export class Song extends BaseEntity { @PrimaryGeneratedColumn("uuid") ID: string; - @Column("uuid", { nullable: true, default: null }) - PID: string; + @Column("uuid", { nullable: true }) + PID?: string; @ManyToOne(() => User, U => U.CreatedTracks) Author: User; @@ -106,7 +106,7 @@ export class Song extends BaseEntity { Setup() { this.ID = v4(); - if (this.PID == undefined) // im lazy but this will work regardless + if (this.PID === undefined) // im lazy but this will work regardless this.PID = this.ID; // By default they should be the same to save space, if we *need* a preview stream later we can change this when processing audio this.Directory = `${SAVED_DATA_PATH}/Songs/${this.ID}`; From c8ad8bd3813264224aee871bf483243059f23fc5 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Wed, 7 Feb 2024 15:11:12 +0000 Subject: [PATCH 09/13] Add .ogg to formats listed on submission page --- src/routes/TrackSubmission.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/TrackSubmission.tsx b/src/routes/TrackSubmission.tsx index 4c77c77..b72252c 100644 --- a/src/routes/TrackSubmission.tsx +++ b/src/routes/TrackSubmission.tsx @@ -91,7 +91,7 @@ export function TrackSubmission() { You can use the #tools-and-resources channel to find useful resources on how to create MIDIs. - Audio File (.m4a, .mp3, .wav) + Audio File (.m4a, .mp3, .wav, .ogg) This will play in the background of your song. Make sure it was exported from REAPER. From eb6d471b2070d006e1caec926166a0d84e98673c Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Wed, 7 Feb 2024 15:11:24 +0000 Subject: [PATCH 10/13] Cap preview streams to 30 seconds --- Server/Source/Routes/Drafting.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index f7ccb8e..00a3fe4 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -254,6 +254,7 @@ App.post("/upload/audio", "-use_timeline 1", "-f dash", "-ac 2", // downmix + "-t 30" // max of 30 seconds (requested by mc) ]) .output(`${AudioPath}/PreviewChunks/PreviewManifest.mpd`) .on("start", cl => Debug(`Creating preview stream with ${magenta(cl)}`)) From 894e9f67f53826a4b950413ea2dd0de08479970d Mon Sep 17 00:00:00 2001 From: McMistrzYT <56406996+McMistrzYT@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:04:49 +0100 Subject: [PATCH 11/13] nothin major --- Server/.example.env | 3 ++- Server/Source/Routes/Authentication.ts | 1 - src/routes/AdminHome.tsx | 4 ++-- src/routes/Home.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/.example.env b/Server/.example.env index 0863aef..74527e9 100644 --- a/Server/.example.env +++ b/Server/.example.env @@ -3,11 +3,12 @@ BODY_SIZE_LIMIT=50mb PROJECT_NAME=Partypack Service SERVER_URL=localhost DASHBOARD_URL=localhost:5173 # set this to wherever you're hosting the Partypack dashboard +USE_HTTPS=true # set this to false if you're debugging on your computer ENVIRONMENT=dev COOKIE_SIGN_KEY= ADMIN_KEY= +JWT_KEY= BOT_TOKEN= -USE_HTTPS=true # set this to false if you're debugging on your computer MAX_AMOUNT_OF_DRAFTS_AT_ONCE=30 DISCORD_SERVER_ID= diff --git a/Server/Source/Routes/Authentication.ts b/Server/Source/Routes/Authentication.ts index a018bd5..44c2f17 100644 --- a/Server/Source/Routes/Authentication.ts +++ b/Server/Source/Routes/Authentication.ts @@ -15,7 +15,6 @@ const App = Router(); // ? hacky, if you want, make it less hacky async function QuickRevokeToken(res: Response, Token: string) { - await fetch("https://discord.com/api/oauth2/token/revoke", { method: "POST", headers: { diff --git a/src/routes/AdminHome.tsx b/src/routes/AdminHome.tsx index 1ccbe14..fa1c1e7 100644 --- a/src/routes/AdminHome.tsx +++ b/src/routes/AdminHome.tsx @@ -12,9 +12,9 @@ export function AdminHome() { Partypack Admin Management Panel - TEMP + Welcome! Please select a management page you'd like to visit: - + diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 4847a5f..cff96e8 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -14,7 +14,7 @@ export function Home() { Partypack was created by everyone listed here. If you're interested in hosting your own instance of Partypack, be sure to check the GitHub repo. Quickstart Guide - Consider watching the easier to understand, visual guide available here.
+ Consider watching the easier to understand, visual guide available in the Discord server.
1. Join this instance's Discord server
2. Click on the icon in the top right
3. Log in using your Discord account
From 698fc4a7b562ab5c29dbdb978ac05872845744b3 Mon Sep 17 00:00:00 2001 From: McMistrzYT <56406996+McMistrzYT@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:16:16 +0100 Subject: [PATCH 12/13] bring back axios --- Server/Source/Handlers/Server.ts | 6 +-- Server/Source/Modules/FNUtil.ts | 31 +++-------- Server/Source/Routes/Authentication.ts | 23 ++++---- Server/Source/Routes/Pages.ts | 7 +-- Server/package-lock.json | 72 ++++++++++++++++++++++++++ Server/package.json | 1 + 6 files changed, 97 insertions(+), 43 deletions(-) diff --git a/Server/Source/Handlers/Server.ts b/Server/Source/Handlers/Server.ts index 26c2120..b68f4d7 100644 --- a/Server/Source/Handlers/Server.ts +++ b/Server/Source/Handlers/Server.ts @@ -68,7 +68,7 @@ async function Initialize() { Initialize(); // ! FESTIVAL-SPECIFIC STUFF -import axios from "axios"; +//import axios from "axios"; -axios.defaults.validateStatus = () => true; -axios.defaults.headers.common["X-Do-Not-Redirect"] = "true"; \ No newline at end of file +//axios.defaults.validateStatus = () => true; +//axios.defaults.headers.common["X-Do-Not-Redirect"] = "true"; \ No newline at end of file diff --git a/Server/Source/Modules/FNUtil.ts b/Server/Source/Modules/FNUtil.ts index a34c58f..206dd52 100644 --- a/Server/Source/Modules/FNUtil.ts +++ b/Server/Source/Modules/FNUtil.ts @@ -1,3 +1,4 @@ +import axios from "axios"; import { Err } from "./Logger"; import { red } from "colorette"; import { FULL_SERVER_ROOT } from "./Constants"; @@ -11,31 +12,15 @@ let LastContentDownloadDate: Date = new Date(0); // set it to 1970 as default cu GenerateFortnitePages(null); export async function GenerateFortnitePages(ForUser: User | null): Promise<{ Success: boolean, FNPages: { [key: string]: unknown } | null }> { - let status; - let data; - if (FullFortnitePages === null || Date.now() > LastContentDownloadDate.getTime() + 30 * 60 * 1000) { - const response = await fetch("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game"); - status = response.status; - data = await response.json(); - } else { - status = 200; - data = FullFortnitePages; - } - /*const { status, data } = // check if 30 minutes have passed since last content update. if so, get a new copy of pages, if not, fuck off + const { status, data } = // check if 30 minutes have passed since last content update. if so, get a new copy of pages, if not, fuck off FullFortnitePages === null || Date.now() > LastContentDownloadDate.getTime() + 30 * 60 * 1000 ? - await fetch("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game") : - FullFortnitePages;*/ + await axios.get("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game") : + { status: 200, data: FullFortnitePages }; - let OGSparks; - - if (OriginalSparks === null || Date.now() > LastContentDownloadDate.getTime() + 30 * 60 * 1000) { - const response = await fetch("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game/spark-tracks"); - OGSparks = {status: 200, data: await response.json()} - } else { - OGSparks = OriginalSparks - } - - + const OGSparks = + OriginalSparks === null || Date.now() > LastContentDownloadDate.getTime() + 30 * 60 * 1000 ? + await axios.get("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game/spark-tracks") : + { status: 200, data: OriginalSparks }; FullFortnitePages = { ...data, diff --git a/Server/Source/Routes/Authentication.ts b/Server/Source/Routes/Authentication.ts index 44c2f17..6a411ec 100644 --- a/Server/Source/Routes/Authentication.ts +++ b/Server/Source/Routes/Authentication.ts @@ -1,3 +1,4 @@ +import axios from "axios"; import jwt from "jsonwebtoken"; import qs from "querystring"; import j from "joi"; @@ -15,14 +16,11 @@ const App = Router(); // ? hacky, if you want, make it less hacky async function QuickRevokeToken(res: Response, Token: string) { - await fetch("https://discord.com/api/oauth2/token/revoke", { - method: "POST", + await axios.post("https://discord.com/api/oauth2/token/revoke", qs.stringify({ token: Token, token_type_hint: "access_token" }), { headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": `Basic ${Buffer.from(`${DISCORD_CLIENT_ID}:${DISCORD_CLIENT_SECRET}`).toString("base64")}` - }, - body: qs.stringify({ token: Token, token_type_hint: "access_token" }) - + } }) return res; } @@ -38,36 +36,33 @@ App.get("/discord", //const Discord = await axios.post(`https://discord.com/api/oauth2/token`, qs.stringify({ grant_type: "authorization_code", code: req.query.code as string, redirect_uri: `${FULL_SERVER_ROOT}/api/discord` }), { auth: { username: DISCORD_CLIENT_ID!, password: DISCORD_CLIENT_SECRET! } }); - const Discord = await fetch( + const Discord = await axios.post( "https://discord.com/api/oauth2/token", + qs.stringify({ grant_type: "authorization_code", code: req.query.code as string, redirect_uri: `${FULL_SERVER_ROOT}/api/discord` }), { - method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": `Basic ${Buffer.from(`${DISCORD_CLIENT_ID}:${DISCORD_CLIENT_SECRET}`).toString("base64")}` - }, - body: qs.stringify({ grant_type: "authorization_code", code: req.query.code as string, redirect_uri: `${FULL_SERVER_ROOT}/api/discord` }) + } } ) if (Discord.status !== 200) return res.status(500).send("Failed to request OAuth token from Discord's services."); - const DiscordData = await Discord.json() as any; // :waaaaa: + const DiscordData = Discord.data; // :waaaaa: if (!DiscordData.scope.includes("identify")) return (await QuickRevokeToken(res, DiscordData.access_token)).status(400).send("Missing identify scope. Please check if your OAuth link is correctly set up!"); - const UserData = await fetch("https://discord.com/api/v10/users/@me", { - method: "GET", + const UserData = await axios.get("https://discord.com/api/v10/users/@me", { headers: { "Authorization": `${DiscordData.token_type} ${DiscordData.access_token}` } - }) - const UserDataBody = await UserData.json() as any; + const UserDataBody = UserData.data; if (UserData.status !== 200) return (await QuickRevokeToken(res, DiscordData.access_token)).status(500).send("Failed to request user data from Discord's services."); diff --git a/Server/Source/Routes/Pages.ts b/Server/Source/Routes/Pages.ts index 8379846..7f09cb8 100644 --- a/Server/Source/Routes/Pages.ts +++ b/Server/Source/Routes/Pages.ts @@ -2,6 +2,7 @@ import { Router } from "express"; import { FullFortnitePages, GenerateFortnitePages } from "../Modules/FNUtil"; import { IS_DEBUG } from "../Modules/Constants"; import { RequireAuthentication } from "../Modules/Middleware"; +import axios from "axios"; const App = Router(); @@ -41,11 +42,11 @@ App.get("/content/api/pages/fortnite-game/:Section", RequireAuthentication(), as if (!CachedSection) return res.status(404).send("funny section not found haha kill me"); - const ContentFromServer = await fetch(`https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game/${CachedSection._title}`) + const ContentFromServer = await axios.get(`https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game/${CachedSection._title}`) if (ContentFromServer.status !== 200) - return res.status(404).json({ error: IS_DEBUG ? await ContentFromServer.text() : "Fortnite server returned an error." }); + return res.status(404).json({ error: IS_DEBUG ? ContentFromServer.data : "Fortnite server returned an error." }); - res.json(await ContentFromServer.json()); + res.json(ContentFromServer.data); }) export default { diff --git a/Server/package-lock.json b/Server/package-lock.json index 452203d..3fdf44e 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@types/node-cron": "^3.0.11", + "axios": "^1.6.7", "better-sqlite3": "^9.3.0", "colorette": "^2.0.20", "cookie-parser": "^1.4.6", @@ -546,6 +547,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -915,6 +931,17 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1032,6 +1059,14 @@ "node": ">=4.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1280,6 +1315,25 @@ "which": "bin/which" } }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -1295,6 +1349,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1960,6 +2027,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/Server/package.json b/Server/package.json index df325db..a0609c6 100644 --- a/Server/package.json +++ b/Server/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "@types/node-cron": "^3.0.11", + "axios": "^1.6.7", "better-sqlite3": "^9.3.0", "colorette": "^2.0.20", "cookie-parser": "^1.4.6", From fe7c678955f61cb5584ebbbfebdcd4a5a61f33e1 Mon Sep 17 00:00:00 2001 From: McMistrzYT <56406996+McMistrzYT@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:52:26 +0100 Subject: [PATCH 13/13] fix authentication requirement for sub-pages --- Server/Source/Routes/Downloads.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index 7fc566f..ec5dd7d 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -5,6 +5,7 @@ import { CreateBlurl } from "../Modules/BLURL"; import { Song } from "../Schemas/Song"; import { RequireAuthentication } from "../Modules/Middleware"; import { UserPermissions } from "../Schemas/User"; +import path from "path"; const App = Router(); @@ -77,11 +78,18 @@ async (req, res) => { }); App.get("/:InternalID", +(req, res, next) => { + // send back index.html when the internal id is not a uuid - causes issues with reloading on sub-pages otherwise + if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(req.params.InternalID)) + return res.sendFile(path.join(process.cwd(), "dist", "index.html")); + + next(); +}, RequireAuthentication(), -async (req, res, next) => { +async (req, res) => { const SongData = await Song.findOne({ where: [ { ID: req.params.InternalID }, { PID: req.params.InternalID } ], relations: { Author: true } }); if (!SongData) - return next(); // trust me bro + return res.status(404).send("Track not found."); const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID;