diff --git a/Saved/Songs/Example/Config.json b/Saved/Songs/Example/Config.json
deleted file mode 100644
index e4b457e..0000000
--- a/Saved/Songs/Example/Config.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "Name": "Dubidubidu",
- "Year": 2003,
- "Artist": "Christell",
- "Length": 224,
- "UUID": "runit",
- "PreviewTime": 0,
- "MinorMajor": "Minor",
- "Key": "A",
- "Album": "Christell",
- "GuitarType": "Keytar",
- "BeatsPerMinute": 145,
- "AssetID": "SID_Placeholder_59",
- "JoinCode": "0000-0000-0000",
- "LipsyncData": "https://cdn2.unrealengine.com/saveyourtears-3b556f295b86.lad",
- "Cover": "https://steamuserimages-a.akamaihd.net/ugc/2319980071298431643/13F80F955F3612EF407370E522ABA956D690374E/?imw=512&&ima=fit&impolicy=Letterbox&imcolor=%23000000&letterbox=false"
-}
\ No newline at end of file
diff --git a/Saved/Songs/Example/Dogsong.mp3 b/Saved/Songs/Example/Dogsong.mp3
deleted file mode 100644
index c0197b5..0000000
Binary files a/Saved/Songs/Example/Dogsong.mp3 and /dev/null differ
diff --git a/Server/Saved/Songs/ButterBarnHoedown/Cover.png b/Server/Saved/Songs/ButterBarnHoedown/Cover.png
new file mode 100644
index 0000000..99331c5
Binary files /dev/null and b/Server/Saved/Songs/ButterBarnHoedown/Cover.png differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00001.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00001.m4s
new file mode 100644
index 0000000..eb617fd
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00001.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00002.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00002.m4s
new file mode 100644
index 0000000..9908a37
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00002.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00003.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00003.m4s
new file mode 100644
index 0000000..653867c
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00003.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00004.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00004.m4s
new file mode 100644
index 0000000..2dc6e1a
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00004.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00005.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00005.m4s
new file mode 100644
index 0000000..c0c4bde
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00005.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00006.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00006.m4s
new file mode 100644
index 0000000..f34178d
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00006.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00007.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00007.m4s
new file mode 100644
index 0000000..d2af797
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00007.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00008.m4s b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00008.m4s
new file mode 100644
index 0000000..62bd3e0
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/chunk-stream0-00008.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Chunks/init-stream0.m4s b/Server/Saved/Songs/Dogsong/Chunks/init-stream0.m4s
new file mode 100644
index 0000000..dcd866d
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Chunks/init-stream0.m4s differ
diff --git a/Server/Saved/Songs/Dogsong/Cover.png b/Server/Saved/Songs/Dogsong/Cover.png
new file mode 100644
index 0000000..dd660ca
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Cover.png differ
diff --git a/Server/Saved/Songs/Dogsong/Data.mid b/Server/Saved/Songs/Dogsong/Data.mid
new file mode 100644
index 0000000..50e8d7b
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Data.mid differ
diff --git a/Server/Saved/Songs/Dogsong/Dogsong.m4a b/Server/Saved/Songs/Dogsong/Dogsong.m4a
new file mode 100644
index 0000000..1962042
Binary files /dev/null and b/Server/Saved/Songs/Dogsong/Dogsong.m4a differ
diff --git a/Server/Saved/Songs/Dogsong/Manifest.mpd b/Server/Saved/Songs/Dogsong/Manifest.mpd
new file mode 100644
index 0000000..2267abf
--- /dev/null
+++ b/Server/Saved/Songs/Dogsong/Manifest.mpd
@@ -0,0 +1,31 @@
+
+
+ {BASEURL}
+
+ Dogsong
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Server/Saved/Songs/Zombies/Audio.m4a b/Server/Saved/Songs/Zombies/Audio.m4a
new file mode 100644
index 0000000..85b4412
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Audio.m4a differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00001.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00001.m4s
new file mode 100644
index 0000000..06ff27c
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00001.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00002.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00002.m4s
new file mode 100644
index 0000000..76ef949
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00002.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00003.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00003.m4s
new file mode 100644
index 0000000..797f479
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00003.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00004.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00004.m4s
new file mode 100644
index 0000000..a9d8aa8
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00004.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00005.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00005.m4s
new file mode 100644
index 0000000..901a925
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00005.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00006.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00006.m4s
new file mode 100644
index 0000000..c75ede1
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00006.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00007.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00007.m4s
new file mode 100644
index 0000000..d9ad70f
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00007.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00008.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00008.m4s
new file mode 100644
index 0000000..a91ee19
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00008.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00009.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00009.m4s
new file mode 100644
index 0000000..7b17b47
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00009.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00010.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00010.m4s
new file mode 100644
index 0000000..d2c0cbe
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00010.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00011.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00011.m4s
new file mode 100644
index 0000000..56494d7
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00011.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00012.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00012.m4s
new file mode 100644
index 0000000..f59f35d
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00012.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00013.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00013.m4s
new file mode 100644
index 0000000..3803b5e
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00013.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00014.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00014.m4s
new file mode 100644
index 0000000..6c24bfe
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00014.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00015.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00015.m4s
new file mode 100644
index 0000000..1e07b4c
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00015.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00016.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00016.m4s
new file mode 100644
index 0000000..734a8b5
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00016.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00017.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00017.m4s
new file mode 100644
index 0000000..fffcb20
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00017.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00018.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00018.m4s
new file mode 100644
index 0000000..1e7d7b3
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00018.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00019.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00019.m4s
new file mode 100644
index 0000000..f3f592a
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00019.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00020.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00020.m4s
new file mode 100644
index 0000000..e940d84
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00020.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00021.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00021.m4s
new file mode 100644
index 0000000..402242f
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00021.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00022.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00022.m4s
new file mode 100644
index 0000000..a51f5cd
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00022.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00023.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00023.m4s
new file mode 100644
index 0000000..3de7f6d
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00023.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00024.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00024.m4s
new file mode 100644
index 0000000..1302152
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00024.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00025.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00025.m4s
new file mode 100644
index 0000000..53e30ce
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00025.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00026.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00026.m4s
new file mode 100644
index 0000000..88d7218
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00026.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00027.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00027.m4s
new file mode 100644
index 0000000..115a102
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00027.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00028.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00028.m4s
new file mode 100644
index 0000000..9f98ebb
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00028.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00029.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00029.m4s
new file mode 100644
index 0000000..cfc6a5b
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00029.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00030.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00030.m4s
new file mode 100644
index 0000000..989e109
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00030.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00031.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00031.m4s
new file mode 100644
index 0000000..fe8de88
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00031.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00032.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00032.m4s
new file mode 100644
index 0000000..9614ba6
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00032.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00033.m4s b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00033.m4s
new file mode 100644
index 0000000..6334bae
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/chunk-stream0-00033.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Chunks/init-stream0.m4s b/Server/Saved/Songs/Zombies/Chunks/init-stream0.m4s
new file mode 100644
index 0000000..dcd866d
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Chunks/init-stream0.m4s differ
diff --git a/Server/Saved/Songs/Zombies/Cover.png b/Server/Saved/Songs/Zombies/Cover.png
new file mode 100644
index 0000000..ffb6a32
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Cover.png differ
diff --git a/Server/Saved/Songs/Zombies/Data.mid b/Server/Saved/Songs/Zombies/Data.mid
new file mode 100644
index 0000000..d1f1753
Binary files /dev/null and b/Server/Saved/Songs/Zombies/Data.mid differ
diff --git a/Server/Saved/Songs/Zombies/Manifest.mpd b/Server/Saved/Songs/Zombies/Manifest.mpd
new file mode 100644
index 0000000..ede6cb9
--- /dev/null
+++ b/Server/Saved/Songs/Zombies/Manifest.mpd
@@ -0,0 +1,30 @@
+
+
+ {BASEURL}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Server/Source/Handlers/Server.ts b/Server/Source/Handlers/Server.ts
index e94658c..044f208 100644
--- a/Server/Source/Handlers/Server.ts
+++ b/Server/Source/Handlers/Server.ts
@@ -1,7 +1,7 @@
import e from "express"
import fs from "fs";
import { BODY_SIZE_LIMIT, COOKIE_SIGN_KEY, DASHBOARD_ROOT, ENDPOINT_AUTHENTICATION_ENABLED, ENDPOINT_AUTH_HEADER, ENDPOINT_AUTH_VALUE, IS_DEBUG, PORT, PROJECT_NAME, SERVER_URL } from "../Modules/Constants";
-import { Msg, Warn } from "../Modules/Logger";
+import { Debug, Msg, Warn } from "../Modules/Logger";
import { italic, magenta, red, yellow } from "colorette";
import cookieParser from "cookie-parser";
import path from "path";
@@ -30,6 +30,7 @@ async function Initialize() {
if (ENDPOINT_AUTHENTICATION_ENABLED && req.header(ENDPOINT_AUTH_HEADER as string) !== ENDPOINT_AUTH_VALUE)
return res.status(403).send(`${SERVER_URL} is currently locked behind authentication. Come back later!`);
+ Debug(req.path);
next();
})
@@ -41,6 +42,8 @@ async function Initialize() {
Msg(`Loaded route ${italic(File)}!`);
}
+
+ App.use((_, res) => res.status(404).json({ errorMessage: "Not Found" }));
App.listen(PORT, () => Msg(`${magenta(PROJECT_NAME)} now up on port ${magenta(PORT)} ${(IS_DEBUG ? red("(debug environment)") : "")}`));
}
@@ -48,11 +51,7 @@ async function Initialize() {
Initialize();
// ! FESTIVAL-SPECIFIC STUFF
-// set up both utils for usage
-import { LoadSongs } from "../Modules/FestivalUtil";
-import { CacheFortnitePages } from "../Modules/PagesUtil";
import axios from "axios";
axios.defaults.validateStatus = () => true;
-LoadSongs();
-CacheFortnitePages();
\ No newline at end of file
+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 62659b2..5198f16 100644
--- a/Server/Source/Modules/FNUtil.ts
+++ b/Server/Source/Modules/FNUtil.ts
@@ -2,31 +2,64 @@ import axios from "axios";
import { Err } from "./Logger";
import { red } from "colorette";
import { FULL_SERVER_ROOT } from "./Constants";
-import { AvailableFestivalSongs } from "./FestivalUtil";
+import { User } from "../Schemas/User";
+import { Song } from "../Schemas/Song";
-export let FullFortnitePages: object | null = null;
+export let FullFortnitePages: {[key: string]: any} | null = null;
+export let OriginalSparks: {[key: string]: any} | null = null;
let LastContentDownloadDate: Date = new Date(0); // set it to 1970 as default cuz im not boutta check if its null
-export async function GenerateFortnitePages(): Promise<{ Success: boolean, FNPages: { [key: string]: unknown } | null }> {
+GenerateFortnitePages(null);
+
+export async function GenerateFortnitePages(ForUser: User | null): Promise<{ Success: boolean, FNPages: { [key: string]: unknown } | null }> {
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 axios.get("https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game") :
{ status: 200, data: FullFortnitePages };
- FullFortnitePages = data;
+ 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,
+ sparkTracks: {
+ ...data.sparkTracks,
+ lastModified: new Date().toISOString()
+ }
+ };
+ OriginalSparks = OGSparks.data;
LastContentDownloadDate = new Date();
- if (status !== 200) {
- Err(`Failed to get Fortnite pages: ${red(status)}`);
+ if (!ForUser)
+ return { Success: true, FNPages: null };
+
+ if (status !== 200 || OGSparks.status !== 200) {
+ Err(`Failed to get Fortnite pages: ${red(status)}, ${red(OGSparks.status)}`);
console.log(data);
process.exit(-1); // very big fuck moment, we literally cannot run the server without fortnitepages
}
const AllSongs: { [key: string]: unknown } = {}; // too lazy to actually write a schema for this :D
- for (const Song of AvailableFestivalSongs)
+ const Overrides = ForUser.Library.map(x => { return { ...x, SongData: Song.findOne({ where: { ID: x.SongID } }) }; });
+ const UsersLibrary = await Promise.all(Overrides.map(x => x.SongData));
+
+ for (const Song of UsersLibrary)
{
- AllSongs[Song.UUID] = {
- _title: Song.UUID,
+ if (!Song)
+ continue;
+
+ const OverridingAs = Overrides.find(x => x.SongID === Song.ID);
+ if (!OverridingAs)
+ continue;
+
+ const OriginalTrack = Object.values(OriginalSparks!).find(x => x.track?.ti === `SparksSong:${OverridingAs.Overriding.toLowerCase()}`);
+ if (!OriginalTrack)
+ continue;
+
+ AllSongs[OriginalTrack._title] = {
+ _title: OriginalTrack._title,
_noIndex: false,
_activeDate: "2023-01-01T01:00:00.000Z",
_locale: "en-US",
@@ -34,37 +67,37 @@ export async function GenerateFortnitePages(): Promise<{ Success: boolean, FNPag
lastModified: new Date().toISOString(),
track: {
tt: Song.Name, // tt - Title,
- an: Song.Artist, // an - Artist Name
- mm: Song.MinorMajor, // mm - Minor, Major
+ an: Song.ArtistName, // an - Artist Name
+ mm: Song.Scale, // mm - Minor, Major
mk: Song.Key, // mk - Music Key
ab: Song.Album, // ab - Album
- su: Song.UUID, // su - Song UUID
+ su: OriginalTrack._title, // su - Song UUID
ry: Song.Year, // ry - Release Year
- mt: Song.BeatsPerMinute, // mt - Music Timing (?)
- au: Song.Cover ?? `${FULL_SERVER_ROOT}/song/download/${Song.UUID}/cover.png`, // au - Album Cover
+ mt: Song.Tempo, // mt - Music Timing (?)
+ au: Song.Cover ?? `${FULL_SERVER_ROOT}/song/download/${Song.ID}/cover.png`, // au - Album Cover
gt: [ "Jam-LoopIsUnpitched-Beat" ], // gt - Gameplay Tags (but in a different format: Example.Gameplay.Tag -> Example-Gameplay-Tag)
- ti: `SparksSong:${Song.AssetID.toLowerCase()}`,
- mu: Song.Midi ?? `${FULL_SERVER_ROOT}/song/download/${Song.UUID}/midi.mid`, // mu - Song Midi (if ending with .mid, decrypted, if with .dat, encrypted)
+ ti: `SparksSong:${OverridingAs.Overriding.toLowerCase()}`,
+ mu: Song.Midi ?? `${FULL_SERVER_ROOT}/song/download/${Song.ID}/midi.mid`, // mu - Song Midi (encrypted)
dn: Song.Length, // dn - Track Length (in seconds)
ge: [ "Pop" ], // ge - Genres
- in: { // TODO: fuck with this to make difficulties :+1:
- ba: 1,
- pb: 2,
- pd: 3,
- pg: 4,
- vl: 3,
- ds: 2,
- gr: 1,
+ in: {
+ ba: Song.BassDifficulty,
+ pb: Song.BassDifficulty,
+ pd: Song.DrumsDifficulty,
+ ds: Song.DrumsDifficulty,
+ pg: Song.GuitarDifficulty,
+ gr: Song.GuitarDifficulty,
+ vl: Song.VocalsDifficulty,
_type: "SparkTrackIntensities"
}, // in - Intensities (those white bars you see)
sib: "Bass", // sib - Bass ID to use (only Bass possible)
sid: "Drum", // sid - Drums ID to use (only Drum possible)
- sig: Song.GuitarType, // sig - Guitar ID to use (Keytar/Guitar)
+ sig: Song.GuitarStarterType, // sig - Guitar ID to use (Keytar/Guitar)
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.UUID, // sid - Song UUID
- pid: Song.UUID, // pid - Playlist Asset ID
- title: Song.UUID, // title - Song Name - same as _title
+ sid: Song.ID, // sid - Song UUID
+ pid: Song.ID, // pid - Playlist Asset ID
+ title: OriginalTrack._title, // title - Song Name - same as _title
tracks: [
{
part: "ds", // Drum Set
@@ -87,18 +120,18 @@ export async function GenerateFortnitePages(): Promise<{ Success: boolean, FNPag
vols: [ 4, 4 ]
},
{
- part: "fs", // Fart Set (jk i have no idea) (might be Flare Set??????????????)
+ part: "fs", // Fart Set (jk i have no idea)
channels: [ "FL", "FR" ],
vols: [ 4, 4 ]
}
],
preview: {
- starttime: Song.PreviewTime
+ starttime: 0
}
}),
- ld: Song.LipsyncData, // ld - Lipsync Data (it's literally a uasset)
- jc: Song.JoinCode, // jc - Join Code (UEFN empty island with nothing - possibly downloads assets)
- sn: Song.UUID, // sn - Song Name - same as _title
+ ld: Song.Lipsync ?? OriginalTrack.track.ld, // ld - Lipsync Data (it's literally a uasset)
+ jc: OriginalTrack.track.jc, // jc - Join Code (UEFN empty island with nothing - possibly downloads assets)
+ sn: OriginalTrack._title, // sn - Song Name - same as _title
_type: "SparkTrack"
}
};
diff --git a/Server/Source/Modules/FestivalUtil.ts b/Server/Source/Modules/FestivalUtil.ts
deleted file mode 100644
index 89426dc..0000000
--- a/Server/Source/Modules/FestivalUtil.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { existsSync, lstatSync, readFileSync, readdirSync } from "fs";
-import { SongItemDefinition } from "./Classes";
-import { CacheFortnitePages } from "./PagesUtil";
-import { Debug, Err, Warn } from "./Logger";
-import { magenta, yellow } from "colorette";
-import watch from "node-watch";
-
-export let AvailableFestivalSongs: SongItemDefinition[] = [];
-
-export function LoadSongs() {
- AvailableFestivalSongs =
- readdirSync("../Saved/Songs")
- .filter(f => lstatSync(`../Saved/Songs/${f}`).isDirectory() && existsSync(`../Saved/Songs/${f}/Config.json`))
- .map(f => {
- let Config: SongItemDefinition;
-
- try { Config = JSON.parse(readFileSync(`../Saved/Songs/${f}/Config.json`).toString()); }
- catch { Err(`Config for song ${f} failed to parse. Please make sure it's valid!`); process.exit(-1); }
-
- // todo: validate if it has all the required properities
- Debug(`Added ${magenta(`${Config.Artist} - ${Config.Name} (${Config.UUID})`)} to the list of available Festival songs!`);
-
- return {
- Directory: `../Saved/Songs/${f}`,
- ...Config
- };
- });
-}
-
-watch("../Saved/Songs", { recursive: true }, (Event, Filename) => {
- Warn(`Detected ${yellow("saved songs")} changes in ${yellow(Filename)}. Reloading available songs!`);
- Debug(`${magenta(Event)} on ${magenta(Filename)}`);
- LoadSongs();
- CacheFortnitePages();
-})
\ No newline at end of file
diff --git a/Server/Source/Modules/Middleware.ts b/Server/Source/Modules/Middleware.ts
new file mode 100644
index 0000000..aceb607
--- /dev/null
+++ b/Server/Source/Modules/Middleware.ts
@@ -0,0 +1,35 @@
+import { NextFunction, Request, Response } from "express";
+import { User } from "../Schemas/User";
+import { JwtPayload, verify } from "jsonwebtoken";
+import { IS_DEBUG, JWT_KEY } from "./Constants";
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ user?: User;
+ }
+ }
+}
+
+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).json({ errorMessage: "This endpoint requires authorization." });
+
+ let JWT: JwtPayload;
+ try {
+ JWT = verify(req.header("X-Partypack-Token") ?? req.cookies["Token"] ?? req.header("Authorization"), JWT_KEY!) as JwtPayload;
+ } catch (err) {
+ console.error(err);
+ return res.status(403).json({ errorMessage: `Invalid Partypack token provided.${IS_DEBUG ? ` (${err})` : ""}` });
+ }
+
+ const UserData = await User.findOne({ where: { ID: JWT.ID }, relations: Relations });
+ if (!UserData)
+ return res.status(401).json({ errorMessage: "Invalid Partypack token provided. User does not exist in database. Please contact an instance admin." });
+
+ req.user = UserData;
+ next();
+ }
+}
\ No newline at end of file
diff --git a/Server/Source/Modules/PagesUtil.ts b/Server/Source/Modules/PagesUtil.ts
deleted file mode 100644
index 1cd793c..0000000
--- a/Server/Source/Modules/PagesUtil.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { GenerateFortnitePages } from "../Modules/FNUtil";
-import { Msg } from "../Modules/Logger";
-
-export let CachedSongFortnitePages: { [key: string]: unknown } | null = null;
-
-export async function CacheFortnitePages() {
- const Result = await GenerateFortnitePages();
- if (!Result.Success)
- return; // fuck we're fucked
-
- CachedSongFortnitePages = Result.FNPages;
- Msg("Successfully reloaded Fortnite pages!");
-}
\ No newline at end of file
diff --git a/Server/Source/Routes/Admin.ts b/Server/Source/Routes/Admin.ts
index 4e06364..d2c691a 100644
--- a/Server/Source/Routes/Admin.ts
+++ b/Server/Source/Routes/Admin.ts
@@ -1,5 +1,6 @@
import { Router } from "express";
import { ADMIN_KEY } from "../Modules/Constants";
+import { Song } from "../Schemas/Song";
const App = Router();
@@ -11,7 +12,7 @@ App.use((req, res, next) => {
if (req.path === "/key")
return res.status(req.body.Key === ADMIN_KEY ? 200 : 403).send(req.body.Key === ADMIN_KEY ? "Login successful!" : "Key doesn't match. Try again.");
- if (req.cookies["AdminKey"] !== ADMIN_KEY)
+ if ((req.cookies["AdminKey"] ?? req.header("Authorization")) !== ADMIN_KEY)
return res.status(403).send("You don't have permission to access this endpoint.");
next();
@@ -19,6 +20,10 @@ App.use((req, res, next) => {
App.get("/test", (_, res) => res.send("Permission check OK"));
+App.get("/tracks", async (_, res) => res.json((await Song.find()).map(x => x.Package())));
+
+App.post("/create/song", async (req, res) => res.json(await Song.create(req.body).save()));
+
export default {
App,
DefaultAPI: "/admin/api"
diff --git a/Server/Source/Routes/Authentication.ts b/Server/Source/Routes/Authentication.ts
index 54fda10..5db90ae 100644
--- a/Server/Source/Routes/Authentication.ts
+++ b/Server/Source/Routes/Authentication.ts
@@ -3,6 +3,7 @@ import jwt from "jsonwebtoken";
import qs from "querystring";
import { Response, Router } from "express";
import { DASHBOARD_ROOT, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, FULL_SERVER_ROOT, JWT_KEY } from "../Modules/Constants";
+import { User } from "../Schemas/User";
const App = Router();
@@ -35,6 +36,12 @@ App.get("/discord", async (req, res) => {
await QuickRevokeToken(res, Discord.data.access_token);
+ if (!await User.exists({ where: { ID: UserData.data.id } }))
+ await User.create({
+ ID: UserData.data.id,
+ Library: []
+ }).save();
+
res
.cookie("Token", jwt.sign({ ID: UserData.data.id }, JWT_KEY!, { algorithm: "HS256" }))
.cookie("UserDetails", Buffer.from(JSON.stringify({ ID: UserData.data.id, Username: UserData.data.username, GlobalName: UserData.data.global_name, Avatar: `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp` })).toString("base64"))
diff --git a/Server/Source/Routes/Discovery.ts b/Server/Source/Routes/Discovery.ts
new file mode 100644
index 0000000..6c2733b
--- /dev/null
+++ b/Server/Source/Routes/Discovery.ts
@@ -0,0 +1,24 @@
+import { Router } from "express";
+import { ForcedCategory } from "../Schemas/ForcedCategory";
+import { Song } from "../Schemas/Song";
+
+const App = Router();
+
+App.get("/", async (req, res) => {
+ const ForcedCategories = await ForcedCategory.find({ where: { Activated: true } });
+ const New = {
+ ID: "new",
+ Header: "Recently added",
+ Songs: (await Song.find({ take: 10, order: { CreationDate: "DESC" } })).map(x => x.Package())
+ }
+
+ res.json([
+ ...ForcedCategories,
+ New
+ ])
+});
+
+export default {
+ App,
+ DefaultAPI: "/api/discovery"
+}
\ No newline at end of file
diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts
index bbc400a..8835455 100644
--- a/Server/Source/Routes/Downloads.ts
+++ b/Server/Source/Routes/Downloads.ts
@@ -1,17 +1,18 @@
import { Router } from "express";
import { existsSync, readFileSync } from "fs";
-import { AvailableFestivalSongs } from "../Modules/FestivalUtil";
import { FULL_SERVER_ROOT } from "../Modules/Constants";
import { CreateBlurl } from "../Modules/BLURL";
+import { Song } from "../Schemas/Song";
const App = Router();
-App.get("/song/download/:SongUUID/:File", (req, res) => {
- const Song = AvailableFestivalSongs.find(x => x.UUID === req.params.SongUUID);
- if (!Song)
- return res.sendStatus(404);
+App.get("/song/download/:InternalID/:File", async (req, res) => {
+ //const Song = AvailableFestivalSongs.find(x => x.UUID === req.params.SongUUID);
+ const SongData = await Song.findOne({ where: { ID: req.params.InternalID } });
+ if (!SongData)
+ return res.status(404).json({ errorMessage: "Song not found." });
- const BaseURL = `${FULL_SERVER_ROOT}/song/download/${Song.UUID}/`;
+ const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`;
switch (req.params.File.toLowerCase()) {
case "master.blurl":
case "main.blurl":
@@ -22,7 +23,7 @@ App.get("/song/download/:SongUUID/:File", (req, res) => {
type: "main",
language: "en",
url: `${BaseURL}master.blurl`,
- data: readFileSync(`${Song.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)
+ data: readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)
}
],
type: "vod",
@@ -32,11 +33,11 @@ App.get("/song/download/:SongUUID/:File", (req, res) => {
case "manifest":
case "manifest.mpd":
- return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(`${Song.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)));
+ return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)));
case "cover":
case "cover.png":
- return existsSync(`${Song.Directory}/Cover.png`) ? res.set("content-type", "image/png").send(readFileSync(`${Song.Directory}/Cover.png`)) : res.sendStatus(404);
+ return existsSync(`${SongData.Directory}/Cover.png`) ? res.set("content-type", "image/png").send(readFileSync(`${SongData.Directory}/Cover.png`)) : res.sendStatus(404);
// ! we are not risking a lawsuit
//case "midi.dat": // dont forget to encrypt!
@@ -46,7 +47,7 @@ App.get("/song/download/:SongUUID/:File", (req, res) => {
case "midi":
case "midi.mid":
case "midi.midi": // forget to encrypt!
- return existsSync(`${Song.Directory}/Data.mid`) ? res.set("content-type", "application/octet-stream").send(readFileSync(`${Song.Directory}/Data.mid`)) : res.sendStatus(404);
+ return existsSync(`${SongData.Directory}/Data.mid`) ? res.set("content-type", "application/octet-stream").send(readFileSync(`${SongData.Directory}/Data.mid`)) : res.sendStatus(404);
}
if (!/^[\w\-.]+$/g.test(req.params.File))
@@ -55,12 +56,32 @@ App.get("/song/download/:SongUUID/:File", (req, res) => {
if (!req.params.File.endsWith(".m4s"))
return res.sendStatus(403);
- if (!existsSync(`${Song.Directory}/Chunks/${req.params.File}`))
+ if (!existsSync(`${SongData.Directory}/Chunks/${req.params.File}`))
return res.sendStatus(404);
res.set("content-type", "video/mp4")
- res.send(readFileSync(`${Song.Directory}/Chunks/${req.params.File}`));
-})
+ res.send(readFileSync(`${SongData.Directory}/Chunks/${req.params.File}`));
+});
+
+App.get("/:InternalID", async (req, res) => {
+ const SongData = await Song.findOne({ where: { ID: req.params.InternalID } });
+ if (!SongData)
+ return res.status(404).json({ errorMessage: "Song not found." });
+
+ const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`;
+ res.set("content-type", "application/json");
+ res.json({
+ playlist: Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)).toString("base64"),
+ playlistType: "application/dash+xml",
+ metadata: {
+ assetId: "",
+ baseUrls: [ BaseURL ],
+ supportsCaching: true,
+ ucp: "a",
+ version: Math.floor(Date.now() / 1000)
+ }
+ });
+});
export default {
App
diff --git a/Server/Source/Routes/Library.ts b/Server/Source/Routes/Library.ts
index 1004a83..35d3279 100644
--- a/Server/Source/Routes/Library.ts
+++ b/Server/Source/Routes/Library.ts
@@ -1,13 +1,91 @@
import { Router } from "express";
+import { RequireAuthentication } from "../Modules/Middleware";
+import { Song } from "../Schemas/Song";
+import { OriginalSparks } from "../Modules/FNUtil";
const App = Router();
-App.get("/song/data/:InternalID", (req, res) => {
+App.get("/me", RequireAuthentication({ BookmarkedSongs: true }), (req, res) => {
res.json({
-
+ Bookmarks: req.user?.BookmarkedSongs.map(x => x.Package()),
+ Library: req.user?.Library
})
})
+App.post("/me/activate", RequireAuthentication(), async (req, res) => {
+ if (!req.body.SongID || !req.body.ToOverride)
+ return res.status(400).json({ errorMessage: "You didn't provide a Song ID." });
+
+ if (!/^sid_placeholder_(\d){1,3}$/gi.test(req.body.ToOverride))
+ return res.status(400).json({ errorMessage: "Field \"ToOverride\" must match \"sid_placeholder_\"" });
+
+ if (req.user?.Library.findIndex(x => x.SongID.toLowerCase() === req.body.SongID.toLowerCase() || x.Overriding.toLowerCase() === req.body.ToOverride.toLowerCase()) !== -1)
+ return res.status(400).json({ errorMessage: "This song is already activated." });
+
+ if (!await Song.exists({ where: { ID: req.body.SongID } }))
+ return res.status(404).json({ errorMessage: "Provided song doesn't exist." });
+
+ req.user?.Library.push({ SongID: req.body.SongID.toLowerCase(), Overriding: req.body.ToOverride.toLowerCase() });
+ req.user?.save();
+
+ res.json(req.user?.Library);
+})
+
+App.post("/me/deactivate", RequireAuthentication(), async (req, res) => {
+ if (!req.body.SongID)
+ return res.status(400).json({ errorMessage: "You didn't provide a Song ID." });
+
+ const idx = req.user!.Library.findIndex(x => x.SongID.toLowerCase() === req.body.SongID.toLowerCase());
+ if (idx === -1)
+ return res.status(400).json({ errorMessage: "This song is not activated." });
+
+ req.user?.Library.splice(idx, 1);
+ req.user?.save();
+
+ res.json(req.user?.Library);
+})
+
+App.post("/me/bookmark", RequireAuthentication({ BookmarkedSongs: true }), async (req, res) => {
+ if (!req.body.SongID)
+ return res.status(400).json({ errorMessage: "You didn't provide a Song ID." });
+
+ if (req.user?.BookmarkedSongs.findIndex(x => x.ID.toLowerCase() === req.body.SongID.toLowerCase()) !== -1)
+ return res.status(400).json({ errorMessage: "This song is already bookmarked." });
+
+ const SongData = await Song.findOne({ where: { ID: req.body.SongID } });
+ if (!SongData)
+ return res.status(404).json({ errorMessage: "Provided song doesn't exist." });
+
+ req.user?.BookmarkedSongs.push(SongData);
+ req.user?.save();
+
+ res.json(req.user?.BookmarkedSongs.map(x => x.Package()));
+})
+
+App.post("/me/unbookmark", RequireAuthentication(), async (req, res) => {
+ if (!req.body.SongID)
+ return res.status(400).json({ errorMessage: "You didn't provide a Song ID." });
+
+ const idx = req.user!.BookmarkedSongs.findIndex(x => x.ID.toLowerCase() === req.body.SongID.toLowerCase());
+ if (idx === -1)
+ return res.status(400).json({ errorMessage: "This song is not bookmarked." });
+
+ req.user?.BookmarkedSongs.splice(idx, 1);
+ req.user?.save();
+
+ res.json(req.user?.BookmarkedSongs.map(x => x.Package()));
+})
+
+App.get("/song/data/:InternalID", async (req, res) => {
+ const SongData = await Song.findOne({ where: { ID: req.params.InternalID } });
+ if (!SongData)
+ return res.status(404).json({ errorMessage: "Song not found." });
+
+ res.json(SongData.Package());
+})
+
+App.get("/available", (__, res) => res.json(Object.values(OriginalSparks!).filter(x => !!x.track).map(x => { return { Name: x.track.tt, Template: x.track.ti.substring(11) }; }).sort((a, b) => a.Name.toLowerCase() > b.Name.toLowerCase() ? 1 : -1)))
+
export default {
App,
DefaultAPI: "/api/library"
diff --git a/Server/Source/Routes/Pages.ts b/Server/Source/Routes/Pages.ts
index 7f28c65..607a5e3 100644
--- a/Server/Source/Routes/Pages.ts
+++ b/Server/Source/Routes/Pages.ts
@@ -1,31 +1,46 @@
import { Router } from "express";
-import { CachedSongFortnitePages } from "../Modules/PagesUtil"; // make sure to import pagesutil after festivalutil :fire:
-import { FullFortnitePages } from "../Modules/FNUtil";
+import { FullFortnitePages, GenerateFortnitePages } from "../Modules/FNUtil";
import axios from "axios";
import { IS_DEBUG } from "../Modules/Constants";
+import { RequireAuthentication } from "../Modules/Middleware";
const App = Router();
-App.get("/content/api/pages/fortnite-game", (_, res) => res.json(FullFortnitePages))
+App.get("/content/api/pages/fortnite-game", (_, res) => res.json({
+ ...FullFortnitePages,
+ sparkTracks: {
+ ...FullFortnitePages!.sparkTracks,
+ _activeDate: "2023-01-01T01:00:00.000Z",
+ lastModified: new Date().toISOString()
+ }
+}))
-App.get("/content/api/pages/fortnite-game/:Section", async (req, res) => {
+App.get("/content/api/pages/fortnite-game/:Section", RequireAuthentication(), async (req, res) => {
if (req.params.Section.toLowerCase() === "spark-tracks") // custom song injection
+ {
+ const ProcessedPages = await GenerateFortnitePages(req.user!);
+
+ res.removeHeader("Access-Control-Allow-Origin");
+ res.removeHeader("Access-Control-Allow-Credentials");
+ res.removeHeader("Vary");
+
return res.json(
{
_title: "spark-tracks",
_noIndex: false,
- _activeDate: "1987-01-01T01:00:00.000Z", // was that the bite of '87
+ _activeDate: "2023-01-01T01:00:00.000Z",
lastModified: new Date().toISOString(),
_locale: "en-US",
_templateName: "blank",
- ...CachedSongFortnitePages,
+ ...ProcessedPages.FNPages,
_suggestedPrefetch: []
}
);
+ }
const CachedSection = Object.values(FullFortnitePages!).find(x => x._title === req.params.Section);
if (!CachedSection)
- return res.status(404).json({ error: "funny section not found haha kill me" });
+ return res.status(404).json({ errorMessage: "funny section not found haha kill me" });
const ContentFromServer = await axios.get(`https://fortnitecontent-website-prod07.ol.epicgames.com/content/api/pages/fortnite-game/${CachedSection._title}`);
if (ContentFromServer.status !== 200)
diff --git a/Server/Source/Schemas/ForcedCategory.ts b/Server/Source/Schemas/ForcedCategory.ts
new file mode 100644
index 0000000..9d0a9ec
--- /dev/null
+++ b/Server/Source/Schemas/ForcedCategory.ts
@@ -0,0 +1,18 @@
+import { BaseEntity, Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
+import { Song } from "./Song";
+
+@Entity()
+export class ForcedCategory extends BaseEntity {
+ @PrimaryGeneratedColumn("uuid")
+ ID: string;
+
+ @Column()
+ Header: string;
+
+ @Column()
+ Activated: boolean;
+
+ @ManyToMany(() => Song, { eager: true })
+ @JoinTable()
+ Songs: Song[];
+}
\ No newline at end of file
diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts
index 39e409f..b1cf97a 100644
--- a/Server/Source/Schemas/Song.ts
+++ b/Server/Source/Schemas/Song.ts
@@ -1,4 +1,5 @@
-import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
+import { BaseEntity, BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
+import { FULL_SERVER_ROOT } from "../Modules/Constants";
@Entity()
export class Song extends BaseEntity {
@@ -33,8 +34,43 @@ export class Song extends BaseEntity {
Tempo: number;
@Column()
- Cover: string;
+ Directory: string;
+
+ @Column({ nullable: true })
+ Midi?: string;
+
+ @Column({ nullable: true })
+ Cover?: string;
+
+ @Column()
+ BassDifficulty: number;
+
+ @Column()
+ GuitarDifficulty: number;
+
+ @Column()
+ DrumsDifficulty: number;
+
+ @Column()
+ VocalsDifficulty: number;
+
+ @Column()
+ CreationDate: Date;
@Column({ nullable: true })
Lipsync?: string;
+
+ @BeforeInsert()
+ Setup() {
+ this.CreationDate = new Date();
+ }
+
+ public Package() {
+ return {
+ ...this,
+ Directory: undefined, // we should NOT reveal that
+ Midi: this.Midi ?? `${FULL_SERVER_ROOT}/song/download/${this.ID}/midi.mid`,
+ Cover: this.Cover ?? `${FULL_SERVER_ROOT}/song/download/${this.ID}/cover.png`
+ }
+ }
}
\ No newline at end of file
diff --git a/Server/Source/Schemas/User.ts b/Server/Source/Schemas/User.ts
index e69de29..ec2e71a 100644
--- a/Server/Source/Schemas/User.ts
+++ b/Server/Source/Schemas/User.ts
@@ -0,0 +1,15 @@
+import { BaseEntity, Column, Entity, JoinTable, ManyToMany, PrimaryColumn } from "typeorm";
+import { Song } from "./Song";
+
+@Entity()
+export class User extends BaseEntity {
+ @PrimaryColumn()
+ ID: string;
+
+ @Column({ type: "simple-json" })
+ Library: { SongID: string, Overriding: string }[];
+
+ @ManyToMany(() => Song, { eager: true })
+ @JoinTable()
+ BookmarkedSongs: Song[];
+}
\ No newline at end of file
diff --git a/Server/package-lock.json b/Server/package-lock.json
index 086b944..5e05acc 100644
--- a/Server/package-lock.json
+++ b/Server/package-lock.json
@@ -18,13 +18,15 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
- "typeorm": "^0.3.19"
+ "typeorm": "^0.3.19",
+ "underscore": "^1.13.6"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.6",
"@types/express": "^4.17.18",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.6.3",
+ "@types/underscore": "^1.11.15",
"tslib": "^2.6.2",
"typescript": "^5.2.2"
}
@@ -178,6 +180,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/underscore": {
+ "version": "1.11.15",
+ "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.15.tgz",
+ "integrity": "sha512-HP38xE+GuWGlbSRq9WrZkousaQ7dragtZCruBVMi0oX1migFZavZ3OROKHSkNp/9ouq82zrWtZpg18jFnVN96g==",
+ "dev": true
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -2166,6 +2174,11 @@
"node": ">=14.17"
}
},
+ "node_modules/underscore": {
+ "version": "1.13.6",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
+ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/Server/package.json b/Server/package.json
index cc2742b..57df92d 100644
--- a/Server/package.json
+++ b/Server/package.json
@@ -33,6 +33,7 @@
"@types/express": "^4.17.18",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.6.3",
+ "@types/underscore": "^1.11.15",
"tslib": "^2.6.2",
"typescript": "^5.2.2"
},
@@ -46,7 +47,8 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
- "typeorm": "^0.3.19"
+ "typeorm": "^0.3.19",
+ "underscore": "^1.13.6"
},
"homepage": "https://github.com/McMistrzYT/BasedServer#readme"
}
diff --git a/Server/tsconfig.json b/Server/tsconfig.json
index 7885afd..af8ccda 100644
--- a/Server/tsconfig.json
+++ b/Server/tsconfig.json
@@ -77,7 +77,7 @@
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
- "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
+ "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */,
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
diff --git a/src/App.tsx b/src/App.tsx
index 431b069..581ba6c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,6 +9,7 @@ import { AdminTrackList } from "./routes/AdminTrackList";
import { AdminHome } from "./routes/AdminHome";
import { AdminLogin } from "./routes/AdminLogin";
import { Download } from "./routes/Download";
+import { Tracks } from "./routes/Tracks";
import { Profile } from "./routes/Profile";
import { SiteContext, SiteState } from "./utils/State";
import merge from "deepmerge";
@@ -36,6 +37,7 @@ function App() {
{/* User-accessible routes */}
} />
} />
+ } />
} />
{/* Admin routes */}
diff --git a/src/components/SiteHeader.tsx b/src/components/SiteHeader.tsx
index 08d8c82..c0e3670 100644
--- a/src/components/SiteHeader.tsx
+++ b/src/components/SiteHeader.tsx
@@ -2,14 +2,14 @@ import { Avatar, Box, Header } from "@primer/react";
import { SignInIcon } from "@primer/octicons-react"
import { useCookies } from "react-cookie";
import { useNavigate } from "react-router-dom";
-import { useContext, useEffect, useState } from "react";
+import { useContext, useEffect } from "react";
import { SiteContext, UserDetailInterface } from "../utils/State";
import { Buffer } from "buffer/";
import Favicon from "../assets/favicon.webp";
import axios from "axios";
+import { toast } from "react-toastify";
export function SiteHeader() {
- const [discordUrl, setDiscordUrl] = useState("");
const {state, setState} = useContext(SiteContext);
const [cookies] = useCookies();
const navigate = useNavigate();
@@ -17,22 +17,14 @@ export function SiteHeader() {
useEffect(() => {
(async () => {
const Data = await axios.get("/api/discord/url");
- if (Data.status === 200)
- setDiscordUrl(Data.data);
+ const Details: UserDetailInterface = cookies["UserDetails"] ? JSON.parse(decodeURI(Buffer.from(cookies["UserDetails"], "base64").toString())) : null;
+
+ setState({
+ ...state,
+ UserDetails: Details,
+ DiscordOauthURL: Data.data
+ });
})();
- }, []);
-
- useEffect(() => {
- if (!cookies["UserDetails"])
- return;
-
- const Details: UserDetailInterface = JSON.parse(decodeURI(Buffer.from(cookies["UserDetails"], "base64").toString()));
- setState({
- ...state,
- UserDetails: Details
- });
-
- console.log(Details);
}, [cookies["UserDetails"]])
return (
@@ -41,18 +33,18 @@ export function SiteHeader() {
Partypack
- Daily Rotation
- Leaderboards
- Tracks
- Tutorials
- FAQ
+ navigate("/rotation")}>Daily Rotation
+ navigate("/creators")}>Top Creators
+ navigate("/tracks")}>Tracks
+ navigate("/tutorials")}>Tutorials
+ navigate("/faq")}>FAQ
window.open("https://discord.gg/KaxknAbqDS")}>Discord
- Download
+ navigate("/download")}>Download
{ cookies["AdminKey"] ? navigate("/admin")} sx={{ cursor: "pointer", color: "danger.emphasis" }}>Admin : <>> }
{
cookies["Token"] && state.UserDetails ?
navigate("/profile")}> :
- discordUrl ? window.location.assign(discordUrl) : console.log("no discord url :(")}>
+ state.DiscordOauthURL ? window.location.assign(state.DiscordOauthURL) : toast("Cannot redirect to login. No Discord OAuth URL has been received from the backend.", { type: "error" })}>
}
);
diff --git a/src/components/Song.tsx b/src/components/Song.tsx
index 0980b79..0d27b6d 100644
--- a/src/components/Song.tsx
+++ b/src/components/Song.tsx
@@ -1,13 +1,13 @@
import { Box, Text } from "@primer/react";
import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
-export function Song({ children }: { children: JSX.Element[] }) {
+export function Song({ data, children }: { data: any, children: JSX.Element[] | JSX.Element | string }) {
return (
-
+
- Someone
- Someone's Song Someone's Song
+ {data.ArtistName}
+ {data.Name}
{children}
diff --git a/src/css/index.css b/src/css/index.css
index f48d3a3..85c72af 100644
--- a/src/css/index.css
+++ b/src/css/index.css
@@ -1,8 +1,14 @@
.content {
padding: 10px;
+ margin-left: 15%;
+ margin-right: 15%;
}
.songCategory {
display: inline-flex;
gap: 10px;
+}
+
+#__primerPortalRoot__ > div {
+ z-index: 1000 !important;
}
\ No newline at end of file
diff --git a/src/routes/AdminTrackList.tsx b/src/routes/AdminTrackList.tsx
index f27a6ec..463a8b3 100644
--- a/src/routes/AdminTrackList.tsx
+++ b/src/routes/AdminTrackList.tsx
@@ -1,9 +1,47 @@
-import { Text } from "@primer/react";
+import axios from "axios";
+import { Box, Button, Heading } from "@primer/react";
+import { useEffect, useState } from "react";
+import { toast } from "react-toastify";
+import { Song } from "../components/Song";
+import { useNavigate } from "react-router-dom";
export function AdminTrackList() {
+ const [tracks, setTracks] = useState([]);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ (async () => {
+ const Tracks = await axios.get("/admin/api/tracks");
+ if (Tracks.status !== 200)
+ return toast("Error while requesting tracks!");
+
+ setTracks(Tracks.data);
+ })();
+ }, []);
+
return (
<>
- Track list
+ All tracks (admin)
+
+ {
+ tracks.map(x => {
+ return
+
+
+
+
+ })
+ }
+
>
)
}
\ No newline at end of file
diff --git a/src/routes/Profile.tsx b/src/routes/Profile.tsx
index dcb74c3..3c5d1e0 100644
--- a/src/routes/Profile.tsx
+++ b/src/routes/Profile.tsx
@@ -1,59 +1,136 @@
-import { Avatar, Box, Button, Heading, Text } from "@primer/react"
+import { ActionList, ActionMenu, Avatar, Box, Button, Dialog, Heading, Text } from "@primer/react"
import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
import { PageHeader } from "@primer/react/drafts";
-import { useContext } from "react";
+import { useContext, useEffect, useState } from "react";
import { SiteContext } from "../utils/State";
import { useCookies } from "react-cookie";
import { useNavigate } from "react-router-dom";
import { Song } from "../components/Song";
+import axios from "axios";
+import { toast } from "react-toastify";
export function Profile() {
- const { state, setState } = useContext(SiteContext);
- const [, , removeCookie] = useCookies();
- const navigate = useNavigate();
+ const { state, setState } = useContext(SiteContext);
+ const [, , removeCookie] = useCookies();
+ const [isActivateDialogOpen, setIsActivateDialogOpen] = useState(false);
+ const [librarySongs, setLibrarySongs] = useState([]);
+ const [bookmarkedSongs, setBookmarkedSongs] = useState([]);
+ const [availableOverrides, setAvailableOverrides] = useState<{ Name: string, Template: string }[]>([]);
+ const [overriding, setOverriding] = useState({});
+ const navigate = useNavigate();
- return (
- <>
- {
- state.UserDetails ?
-
-
-
-
-
-
-
- {state.UserDetails.GlobalName} (@{state.UserDetails.Username})
-
-
-
-
-
-
-
- Active Songs
-
-
- Overriding: Party Rock Anthem
-
-
-
- My Bookmarks
-
-
-
-
-
-
-
-
-
-
- :
- <>
- You are not logged in.
Log in using the button in the top right.
- >
- }
- >
- )
+ useEffect(() => {
+ (async () => {
+ const Data = await axios.get("/api/library/me");
+ const Overrides = await axios.get("/api/library/available");
+
+ if (Data.status !== 200 || Overrides.status !== 200)
+ return toast("An error has occured while getting your library!", { type: "error" });
+
+ const LibSongs = (await Promise.all(Data.data.Library.map((x: { SongID: string; }) => axios.get(`/api/library/song/data/${x.SongID}`)))).map(x => { return { ...x.data, Override: Data.data.Library.find((y: { SongID: string; }) => y.SongID === x.data.ID).Overriding } });
+ const BookSongs = (await Promise.all(Data.data.Bookmarks.map((x: { ID: string; }) => axios.get(`/api/library/song/data/${x.ID}`)))).map(x => x.data);
+ setLibrarySongs(LibSongs);
+ setBookmarkedSongs(BookSongs);
+ setAvailableOverrides(Overrides.data);
+ })();
+ }, []);
+
+ return (
+ <>
+ {
+ state.UserDetails ?
+
+
+
+
+
+
+
+ {state.UserDetails.GlobalName} (@{state.UserDetails.Username})
+
+
+
+
+
+
+
+ Active Songs
+
+
+ {
+ librarySongs.length >= 1 ?
+ librarySongs.map(x => {
+ return
+ Overriding: {availableOverrides.find(y => y.Template.toLowerCase() === x.Override.toLowerCase())?.Name}
+
+ ;
+ })
+ : You have no activated songs.
+ }
+
+ My Bookmarks
+
+ {
+ bookmarkedSongs.length >= 1 ?
+ bookmarkedSongs.map(x => {
+ return
+
+
+ ;
+ })
+ : You have no bookmarked songs.
+ }
+
+ :
+ <>
+ You are not logged in.
Log in using the button in the top right.
+ >
+ }
+ >
+ )
}
\ No newline at end of file
diff --git a/src/routes/Tracks.tsx b/src/routes/Tracks.tsx
new file mode 100644
index 0000000..75c836c
--- /dev/null
+++ b/src/routes/Tracks.tsx
@@ -0,0 +1,79 @@
+import { Box, Button, Heading } from "@primer/react";
+import { Song } from "../components/Song";
+import { useContext, useEffect, useState } from "react";
+import axios from "axios";
+import { toast } from "react-toastify";
+import { SiteContext } from "../utils/State";
+
+export function Tracks() {
+ const [trackData, setTrackData] = useState<{ Header: string, Songs: unknown[] }[]>([]);
+ const [bookmarks, setBookmarks] = useState([]);
+ const {state} = useContext(SiteContext);
+
+ useEffect(() => {
+ (async () => {
+ const Discovery = await axios.get("/api/discovery");
+ if (Discovery.status !== 200)
+ return toast(Discovery.data.errorMessage, { type: "error" });
+
+ setTrackData(Discovery.data);
+
+ const Bookmarks = await axios.get("/api/library/me");
+ if (Bookmarks.status !== 200)
+ return// toast(Bookmarks.data.errorMessage, { type: "error" });
+
+ setBookmarks(Bookmarks.data.Bookmarks);
+ })();
+ }, [])
+
+ return (
+ <>
+ {
+ trackData.map(x => {
+ return
+ {x.Header}
+
+ {
+ x.Songs.map(x => {
+ return
+ {
+ bookmarks.findIndex(y => y.ID === x.ID) !== -1 ?
+ :
+
+ }
+
+ })
+ }
+
+
+ })
+ }
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/utils/State.ts b/src/utils/State.ts
index 3aaf1be..b0f9f0d 100644
--- a/src/utils/State.ts
+++ b/src/utils/State.ts
@@ -6,7 +6,8 @@ export const SiteContext = createContext({} as IContext);
export interface UserDetailInterface { ID: string, Username: string, GlobalName: string, Avatar: string }
export interface SiteState {
- UserDetails: UserDetailInterface | null
+ UserDetails: UserDetailInterface | null;
+ DiscordOauthURL: string | null;
}
export interface IContext {
diff --git a/tsconfig.json b/tsconfig.json
index a7fc6fb..7891d62 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,6 +16,7 @@
/* Linting */
"strict": true,
+ "noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true