From 883d5ca4ec54cef5d02be37decdbf18b25b9db62 Mon Sep 17 00:00:00 2001 From: McMistrzYT <56406996+McMistrzYT@users.noreply.github.com> Date: Sun, 4 Feb 2024 22:28:42 +0100 Subject: [PATCH] mid is really fat like if you agree --- Server/Source/Handlers/Database.ts | 4 +- Server/Source/Handlers/DiscordBot.ts | 20 +++++++++ Server/Source/Routes/Admin.ts | 58 +++++++++++++++++++++---- Server/Source/Routes/Authentication.ts | 51 ++++++++++++++-------- Server/Source/Routes/Debug.ts | 11 ++++- Server/Source/Routes/Downloads.ts | 4 +- Server/Source/Routes/Drafting.ts | 3 +- Server/Source/Schemas/DiscordRole.ts | 21 +++++++++ Server/Source/Schemas/Song.ts | 2 +- Server/Source/Schemas/User.ts | 10 +++++ Server/Source/index.ts | 1 + package.json | 6 +-- src/App.tsx | 4 ++ src/routes/Credits.tsx | 19 ++++++++ src/routes/Download.tsx | 7 +++ src/routes/FrequentlyAskedQuestions.tsx | 46 ++++++++++++++++++++ src/routes/Home.tsx | 24 ++++++++-- src/routes/Profile.tsx | 46 +++++++++++++++++++- src/routes/TrackSubmission.tsx | 13 +++--- 19 files changed, 305 insertions(+), 45 deletions(-) create mode 100644 Server/Source/Handlers/DiscordBot.ts create mode 100644 Server/Source/Schemas/DiscordRole.ts create mode 100644 src/routes/Credits.tsx create mode 100644 src/routes/FrequentlyAskedQuestions.tsx diff --git a/Server/Source/Handlers/Database.ts b/Server/Source/Handlers/Database.ts index 8379665..9e7e5de 100644 --- a/Server/Source/Handlers/Database.ts +++ b/Server/Source/Handlers/Database.ts @@ -4,6 +4,7 @@ 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"; export const DBSource = new DataSource({ type: "better-sqlite3", @@ -14,7 +15,8 @@ export const DBSource = new DataSource({ Song, ForcedCategory, User, - Rating + Rating, + DiscordRole /*join(__dirname, "..", "Schemas") + "\\*{.js,.ts}"*/ // does not work in prod ], subscribers: [], diff --git a/Server/Source/Handlers/DiscordBot.ts b/Server/Source/Handlers/DiscordBot.ts new file mode 100644 index 0000000..cb6adab --- /dev/null +++ b/Server/Source/Handlers/DiscordBot.ts @@ -0,0 +1,20 @@ +import { ActivityType, Client, IntentsBitField } from "discord.js"; +import { Msg } from "../Modules/Logger"; +import { green } from "colorette"; +import { BOT_TOKEN } from "../Modules/Constants"; + +export const Bot: Client = new Client({ + intents: IntentsBitField.Flags.Guilds, + presence: { + status: "online", + activities: [ + { + name: "over Partypack", + type: ActivityType.Watching + } + ] + } +}); + +Bot.on("ready", () => Msg(`Discord bot now ready as ${green(Bot.user.username)}${green("#")}${green(Bot.user.discriminator)}`)); +Bot.login(BOT_TOKEN); \ No newline at end of file diff --git a/Server/Source/Routes/Admin.ts b/Server/Source/Routes/Admin.ts index 5a4dc4e..a5d9730 100644 --- a/Server/Source/Routes/Admin.ts +++ b/Server/Source/Routes/Admin.ts @@ -1,17 +1,13 @@ /* eslint-disable no-case-declarations */ -import { FULL_SERVER_ROOT } from "../Modules/Constants"; +import j from "joi"; import { Router } from "express"; import { UserPermissions } from "../Schemas/User"; -import { Song, SongStatus } from "../Schemas/Song"; +import { Song } from "../Schemas/Song"; import { RequireAuthentication, ValidateBody } from "../Modules/Middleware"; -import { writeFileSync } from "fs"; import { ForcedCategory } from "../Schemas/ForcedCategory"; -import { fromBuffer } from "file-type"; -import { Debug } from "../Modules/Logger"; -import { magenta } from "colorette"; -import ffmpeg from "fluent-ffmpeg"; -import j from "joi"; -import sizeOf from "image-size"; +import { DiscordRole } from "../Schemas/DiscordRole"; +import { Bot } from "../Handlers/DiscordBot"; +import { DISCORD_SERVER_ID } from "../Modules/Constants"; const App = Router(); @@ -28,6 +24,50 @@ App.use((req, res, next) => { next(); }); +App.post("/create/role", +ValidateBody(j.object({ + ID: j.string().min(10).max(32).required(), + Comment: j.string().max(128).optional(), + PermissionLevel: j.number().valid(...(Object.values(UserPermissions).filter(x => !isNaN(Number(x))))).required() +})), +async (req, res) => { + if (!Bot.isReady()) + return res.status(500).send("This Partypack instance has a misconfigured Discord bot."); + + if (!Bot.guilds.cache.get(DISCORD_SERVER_ID as string)?.roles.cache.has(req.body.ID)) + return res.status(404).send("This role does not exist in the Discord server."); + + const Existing = await DiscordRole.findOne({ where: { ID: req.body.ID } }); + if (Existing) { + Existing.GrantedPermissions = req.body.PermissionLevel as UserPermissions; + Existing.Comment = req.body.Comment ?? Existing.Comment; + await Existing.save(); + return res.json(Existing.Package(true)); + } + + const RoleEntry = await DiscordRole.create({ + ID: req.body.ID, + Comment: req.body.Comment ?? "No comment", + GrantedPermissions: req.body.PermissionLevel as UserPermissions + }).save(); + + res.json(RoleEntry.Package(true)); +}); + +App.post("/delete/role", +ValidateBody(j.object({ + ID: j.string().min(10).max(32).required() +})), +async (req, res) => { + const RoleData = await DiscordRole.findOne({ where: { ID: req.body.ID } }); + if (!RoleData) + return res.status(404).send("This role does not exist in the database."); + + await RoleData.remove(); + res.send("Removed role successfully."); +}) + +App.get("/roles", async (_, res) => res.json((await DiscordRole.find()).map(x => x.Package(true)))); App.get("/tracks", async (_, res) => res.json((await Song.find()).map(x => x.Package(true)))); App.post("/update/discovery", diff --git a/Server/Source/Routes/Authentication.ts b/Server/Source/Routes/Authentication.ts index 8f58b75..8adc041 100644 --- a/Server/Source/Routes/Authentication.ts +++ b/Server/Source/Routes/Authentication.ts @@ -3,13 +3,16 @@ import jwt from "jsonwebtoken"; import qs from "querystring"; import j from "joi"; import { Response, Router } from "express"; -import { BOT_TOKEN, DASHBOARD_ROOT, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_SERVER_ID, FULL_SERVER_ROOT, JWT_KEY } from "../Modules/Constants"; +import { DASHBOARD_ROOT, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_SERVER_ID, FULL_SERVER_ROOT, JWT_KEY } from "../Modules/Constants"; import { User, UserPermissions } from "../Schemas/User"; import { ValidateQuery } from "../Modules/Middleware"; -import { Err } from "../Modules/Logger"; +import { Bot } from "../Handlers/DiscordBot"; +import { Debug } from "../Modules/Logger"; +import { DiscordRole } from "../Schemas/DiscordRole"; +import { In } from "typeorm"; +import { magenta } from "colorette"; const App = Router(); -//let DiscordServerRoleMetadata; // ? hacky, if you want, make it less hacky async function QuickRevokeToken(res: Response, Token: string) { @@ -17,18 +20,6 @@ async function QuickRevokeToken(res: Response, Token: string) { return res; } -async function ReloadRoleData() { - const DRMt = await axios.get(`https://discord.com/api/guilds/${DISCORD_SERVER_ID}/roles`, { headers: { Authorization: `Bot ${BOT_TOKEN}` } }); - - Err(`Discord roles request failed to execute. Did you set up the .env correctly?`) - if (DRMt.status !== 200) - process.exit(-1); - - //DiscordServerRoleMetadata = DRMt.data as { id: string, name: string, permissions: number }[]; -} - -//ReloadRoleData(); - App.get("/discord/url", (_ ,res) => res.send(`https://discord.com/api/oauth2/authorize?client_id=${qs.escape(DISCORD_CLIENT_ID!)}&response_type=code&redirect_uri=${qs.escape(`${FULL_SERVER_ROOT}/api/discord`)}&scope=identify`)) App.get("/discord", @@ -51,16 +42,42 @@ async (req, res) => { await QuickRevokeToken(res, Discord.data.access_token); - // TODO: add discord role thingy - let UserPermissionLevel = UserPermissions.User; + const AnyUserExists = await User.exists(); // automatically grant the first user on the database administrator permissions + let UserPermissionLevel = !AnyUserExists ? UserPermissions.Administrator : UserPermissions.User; + + if (AnyUserExists && Bot.isReady()) { + Debug("Using Discord roles to determine user permission level since the Discord bot exists and is ready."); + + const Sewer = Bot.guilds.cache.get(DISCORD_SERVER_ID as string); + const Membuh = await Sewer?.members.fetch(UserData.data.id); + + if (Membuh) { + const RoulInDeightabaise = await DiscordRole.find({ where: { ID: In(Membuh.roles.cache.map(x => x.id)) }, order: { GrantedPermissions: "DESC" } }); + if (RoulInDeightabaise.length > 0) + UserPermissionLevel = RoulInDeightabaise[0].GrantedPermissions; + + Debug(`Detected ${magenta(RoulInDeightabaise.length)} roles that override Database permissions for user ${magenta(`@${UserData.data.username}`)}. Giving permission level ${magenta(UserPermissionLevel)}.`) + } + } let DBUser = await User.findOne({ where: { ID: UserData.data.id } }); if (!DBUser) DBUser = await User.create({ ID: UserData.data.id, + Username: UserData.data.username, + DisplayName: UserData.data.global_name ?? UserData.data.username, + ProfilePictureURL: `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp`, Library: [], PermissionLevel: UserPermissionLevel }).save(); + else + { + DBUser.Username = UserData.data.username; + DBUser.DisplayName = UserData.data.global_name ?? UserData.data.username; + DBUser.ProfilePictureURL = `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp`; + DBUser.PermissionLevel = UserPermissionLevel; + await DBUser.save(); + } const JWT = jwt.sign({ ID: UserData.data.id }, JWT_KEY!, { algorithm: "HS256" }); const 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`, IsAdmin: DBUser.PermissionLevel >= UserPermissions.Administrator, Role: DBUser.PermissionLevel })).toString("hex") diff --git a/Server/Source/Routes/Debug.ts b/Server/Source/Routes/Debug.ts index fe371e6..f8382c7 100644 --- a/Server/Source/Routes/Debug.ts +++ b/Server/Source/Routes/Debug.ts @@ -1,9 +1,10 @@ +import j from "joi"; import { Router } from "express"; -import { ENVIRONMENT } from "../Modules/Constants"; +import { ENVIRONMENT, JWT_KEY } from "../Modules/Constants"; import { RequireAuthentication, ValidateBody } from "../Modules/Middleware"; import { User, UserPermissions } from "../Schemas/User"; -import j from "joi"; import { Song } from "../Schemas/Song"; +import { sign } from "jsonwebtoken"; const App = Router(); @@ -39,6 +40,12 @@ async (req, res) => { res.json(req.user); }) +App.post("/create/auth", +ValidateBody(j.object({ + ID: j.string().min(10).max(25).required() +})), +(req, res) => res.send(sign(req.body, JWT_KEY as string))); + App.get("/raw/song/:SongID", async (req, res) => res.json(await Song.findOne({ where: { ID: req.params.SongID } }))); diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index 5bf228e..6f61f72 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -8,6 +8,8 @@ import { UserPermissions } from "../Schemas/User"; const App = Router(); +App.get("/api/download/partypacker", (_, res) => res.redirect("https://cdn.discordapp.com/attachments/1202728144935583804/1203083689840607252/Partypacker_OT2.zip")) + App.get("/song/download/:InternalID/:File", RequireAuthentication(), async (req, res) => { @@ -60,7 +62,7 @@ async (req, res) => { if (!/^[\w\-.]+$/g.test(req.params.File)) return res.status(400).send("File name failed validation."); - if (!req.params.File.endsWith(".m4s")) + if (!req.params.File.endsWith(".m4s") && !req.params.File.endsWith(".webm")) return res.sendStatus(403); if (!existsSync(`${SongData.Directory}/Chunks/${req.params.File}`)) diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index 55d7ac8..fc97666 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -199,7 +199,8 @@ App.post("/upload/audio", .audioCodec("libopus") .outputOptions([ "-use_timeline 1", - "-f dash" + "-f dash", + "-mapping_family 255" ]) .output(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`) .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) diff --git a/Server/Source/Schemas/DiscordRole.ts b/Server/Source/Schemas/DiscordRole.ts new file mode 100644 index 0000000..5e6ae7b --- /dev/null +++ b/Server/Source/Schemas/DiscordRole.ts @@ -0,0 +1,21 @@ +import { BaseEntity, Column, Entity, PrimaryColumn } from "typeorm"; +import { UserPermissions } from "./User"; + +@Entity() +export class DiscordRole extends BaseEntity { + @PrimaryColumn() + ID: string; + + @Column() + GrantedPermissions: UserPermissions; + + @Column({ default: "No comment" }) + Comment: string; + + public Package(IncludeComment: boolean) { + return { + ...this, + Comment: IncludeComment ? this.Comment : undefined + } + } +} \ No newline at end of file diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index b999cf2..8e0f34b 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -119,7 +119,7 @@ export class Song extends BaseEntity { return { ...this, Status: IncludeStatus ? this.Status : SongStatus.DEFAULT, - Author: this.Author ? this.Author.ID : undefined, + Author: this.Author ? this.Author.Package() : undefined, 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` diff --git a/Server/Source/Schemas/User.ts b/Server/Source/Schemas/User.ts index cc7b8d2..ac9e4fe 100644 --- a/Server/Source/Schemas/User.ts +++ b/Server/Source/Schemas/User.ts @@ -39,4 +39,14 @@ export class User extends BaseEntity { @ManyToMany(() => Song, { eager: true }) @JoinTable() BookmarkedSongs: Song[]; + + public Package(IncludeRatings: boolean = false, IncludeCreatedTracks: boolean = false) { + return { + ...this, + Ratings: IncludeRatings ? this.Ratings : undefined, + Library: undefined, + CreatedTracks: IncludeCreatedTracks ? this.CreatedTracks : undefined, + BookmarkedSongs: undefined + } + } } \ No newline at end of file diff --git a/Server/Source/index.ts b/Server/Source/index.ts index 69497ec..8f25c29 100644 --- a/Server/Source/index.ts +++ b/Server/Source/index.ts @@ -3,6 +3,7 @@ config(); import "./Handlers/Database"; import "./Handlers/Server"; +import "./Handlers/DiscordBot"; /* Welcome to Mc's BasedServer template! (v1.1 - 30.12.2023 update) diff --git a/package.json b/package.json index 97efd3a..cd33836 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "build:prod": "vite build", "build:stage": "vite build --mode staging", "win:create:prod": "mkdir \"./Out\" ; vite build ; move \"./dist\" \"./Out/dist\" ; cd \"Server\" ; tsc ; cd .. ; copy \"./Server/.env.prod\" \"./Out/.env\" ; copy \"./Server/package.json\" \"./Out/package.json\" ; copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", - "win:publish:prod": "npm run win:create:prod ; ssh partypack \"cd /home/PartypackProd; rm -rf ./Out\" ; scp -r \"./Out\" partypack:/home/PartypackProd ; ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" ; rmdir \"./Out\"", - "win:create:stage": "mkdir \"./Out\" && vite build --mode staging && move \"./dist\" \"./Out/dist\" && cd \"Server\" && tsc && cd .. && copy \"./Server/.env.staging\" \"./Out/.env\" && copy \"./Server/package.json\" \"./Out/package.json\" && copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", - "win:publish:stage": "npm run win:create:stage && ssh partypack \"cd /home/PartypackStage; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackStage && ssh partypack \"cd /home/PartypackStage/Out && npm i && pm2 restart PartypackStage --update-env\" && rmdir \"./Out\"", + "win:publish:prod": "npm run win:create:prod ; ssh partypack \"cd /home/PartypackProd && rm -rf ./Out\" ; scp -r \"./Out\" partypack:/home/PartypackProd ; ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" ; rmdir \"./Out\"", + "win:create:stage": "mkdir \"./Out\" ; vite build --mode staging ; move \"./dist\" \"./Out/dist\" ; cd \"Server\" ; tsc ; cd .. ; copy \"./Server/.env.staging\" \"./Out/.env\" ; copy \"./Server/package.json\" \"./Out/package.json\" ; copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", + "win:publish:stage": "mkdir \"./Out\" ; vite build --mode staging ; move \"./dist\" \"./Out/dist\" ; cd \"Server\" ; tsc ; cd .. ; copy \"./Server/.env.staging\" \"./Out/.env\" ; copy \"./Server/package.json\" \"./Out/package.json\" ; copy \"./Server/package-lock.json\" \"./Out/package-lock.json\" ; ssh partypack \"cd /home/PartypackStage && rm -rf ./Out\" ; scp -r \"./Out\" partypack:/home/PartypackStage ; ssh partypack \"cd /home/PartypackStage/Out && npm i && pm2 restart PartypackStage --update-env\" ; rmdir \"./Out\"", "create:prod": "mkdir ./Out && npm run build:prod && mv ./dist ./Out/dist && cd Server && tsc && cd .. && cp ./Server/.env.prod ./Out/.env && cp ./Server/package.json ./Out && cp ./Server/package-lock.json ./Out", "publish:prod": "npm run create:prod && ssh partypack \"cd /home/PartypackProd; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackProd && ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" && rm -rf ./Out", "create:stage": "mkdir ./Out && npm run build:stage && mv ./dist ./Out/dist && cd Server && tsc && cd .. && cp ./Server/.env.staging ./Out/.env && cp ./Server/package.json ./Out && cp ./Server/package-lock.json ./Out", diff --git a/src/App.tsx b/src/App.tsx index aab3721..2c8ac5f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import { Tracks } from "./routes/Tracks"; import { TrackSubmission } from "./routes/TrackSubmission"; import { Profile } from "./routes/Profile"; import { NotFound } from "./routes/404"; +import { Credits } from "./routes/Credits"; import { AdminHome } from "./routes/AdminHome"; import { AdminTrackList } from "./routes/AdminTrackList"; import { AdminSubmissions } from "./routes/AdminSubmissions"; @@ -23,6 +24,7 @@ import merge from "deepmerge"; import "react-toastify/dist/ReactToastify.css"; import "./css/index.css"; +import { FrequentlyAskedQuestions } from "./routes/FrequentlyAskedQuestions"; const DefaultTheme = merge(theme, {}); // we'll use this!! eventually!!! @@ -43,9 +45,11 @@ function App() { {/* User-accessible routes */} } /> } /> + } /> } /> } /> } /> + } /> } /> {/* Staff routes */} diff --git a/src/routes/Credits.tsx b/src/routes/Credits.tsx new file mode 100644 index 0000000..041e1f8 --- /dev/null +++ b/src/routes/Credits.tsx @@ -0,0 +1,19 @@ +import { Heading } from "@primer/react"; + +export function Credits() { + return ( +
+ Partypack +

Created by

+ McMistrzYT
+

Additional help from

+ shady
+ AveryMadness
+ YLS-Dev
+ Samuel
+ Contributors on GitHub +

Special thanks

+ Bryan Griffin
+
+ ) +} \ No newline at end of file diff --git a/src/routes/Download.tsx b/src/routes/Download.tsx index 670da93..c6980d1 100644 --- a/src/routes/Download.tsx +++ b/src/routes/Download.tsx @@ -1,6 +1,13 @@ +import { Button, Heading, Text } from "@primer/react"; + export function Download() { return ( <> +
+ You're one step away from experiencing peak Fortnite Festival + Click the button below to download Partypacker to launch into Fortnite with custom Partypack songs! + +
) } \ No newline at end of file diff --git a/src/routes/FrequentlyAskedQuestions.tsx b/src/routes/FrequentlyAskedQuestions.tsx new file mode 100644 index 0000000..427258b --- /dev/null +++ b/src/routes/FrequentlyAskedQuestions.tsx @@ -0,0 +1,46 @@ +import { Box, Heading, Text } from "@primer/react"; + +const FAQ: { Q: string, A: string }[] = [ + { + Q: "Can custom jam tracks get me banned?", + A: "No, custom songs do not interfere with Fortnite's anti-cheat and will NOT get you banned." + }, + { + Q: "How do custom jam tracks work?", + A: "Using our custom launcher, Partypacker, you are able to redirect Fortnite's requests to the Partypack backend, which modifies Fortnite's tracklist. This allows us to replace existing jam tracks with completely custom jam tracks which include metadata, music, and charts." + }, + { + Q: "Can I play custom jam tracks with friends?", + A: "Yes, you can! ...but they have to activate the exact same songs, replacing the exact same songs as you." + }, + { + Q: "Will stats save for custom jam tracks?", + A: "Unfortunately, stats and leaderboards will not work for custom songs." + }, + { + Q: "Can I play jam tracks aleady inside Fortnite for free as a custom jam track?", + A: "No, this is not currently allowed and will never be allowed on this instance." + }, + { + Q: "Do you have to chart every instrument on every difficulty for the custom jam track to work?", + A: "No, you can chart as little as you want (or none at all) and the jam track will still load just fine. However, there currently is no way to disable a specific instrument or difficulty on a jam track." + }, + { + Q: "Does this have a token logger or spyware hidden inside?", + A: "No! All our source code used for the server and launcher are available on McMistrzYT's GitHub. (links available on the home page)" + } +]; + +export function FrequentlyAskedQuestions() { + return ( + + Frequently Asked Questions + { + FAQ.map(x => <> + Q: {x.Q}
+ A: {x.A}

+ ) + } +
+ ) +} \ No newline at end of file diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 53d2ba5..4847a5f 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -1,13 +1,31 @@ import { Box, Heading, Text } from "@primer/react"; +import { SignInIcon } from "@primer/octicons-react"; export function Home() { return ( <> - PARTYPACK - Placeholder Place Logo Here - Welcome to Partypack! blah blah someone please make this text for me im way too lazy to do allat + Welcome to Partypack + + Welcome to Partypack, the ultimate game-changing experience for Fortnite Festival! Partypack brings custom songs into the game, letting players turn it into a community driven experience! +
+ Here, you can view tracks by other people or submit your own, read tutorials and look at the FAQ for important topics. Be sure to also read the quickstart guide to know how to get started! +
+ 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 - 1. Kill yourself
2. Do it again
+ + Consider watching the easier to understand, visual guide available here.
+ 1. Join this instance's Discord server
+ 2. Click on the icon in the top right
+ 3. Log in using your Discord account
+ 4. Go to Tracks and subscribe to tracks you like
+ 5. Click on your profile picture in the top right to access your Profile page
+ 6. Activate songs by replacing original Fortnite songs
+ 7. Download the Partypacker Launcher from the Download tab
+ 8. Run the Partypacker application, log in using your Discord account and press Launch
+ 9. If any warnings pop up, press YES on all of them for the proxy to work correctly
+ 10. Enter a Festival Main Stage match and try out your custom songs! +
) diff --git a/src/routes/Profile.tsx b/src/routes/Profile.tsx index 5e67b25..a2ac633 100644 --- a/src/routes/Profile.tsx +++ b/src/routes/Profile.tsx @@ -1,6 +1,6 @@ import axios from "axios"; import { Buffer } from "buffer/"; -import { ActionList, ActionMenu, Avatar, Box, Button, Dialog, FormControl, Heading, Text, TextInput } from "@primer/react" +import { ActionList, ActionMenu, Avatar, Box, Button, Dialog, FormControl, Heading, Label, Text, TextInput } from "@primer/react" import { Divider } from "@primer/react/lib-esm/ActionList/Divider"; import { PageHeader } from "@primer/react/drafts"; import { useContext, useEffect, useRef, useState } from "react"; @@ -8,7 +8,8 @@ import { SiteContext } from "../utils/State"; import { useCookies } from "react-cookie"; import { Song } from "../components/Song"; import { toast } from "react-toastify"; -import { SongStatus } from "../utils/Extensions"; +import { SongStatus, UserPermissions } from "../utils/Extensions"; +import { LabelColorOptions } from "@primer/react/lib-esm/Label/Label"; const formControlStyle = { paddingTop: 3 }; @@ -17,6 +18,8 @@ export function Profile() { const { state, setState } = useContext(SiteContext); const [, , removeCookie] = useCookies(); const [isActivateDialogOpen, setIsActivateDialogOpen] = useState(false); + const [variant, setVariant] = useState("success"); + const [labelText, setLabelText] = useState(""); const [librarySongs, setLibrarySongs] = useState([]); const [bookmarkedSongs, setBookmarkedSongs] = useState([]); const [draftsSongs, setDraftsSongs] = useState([]); @@ -25,6 +28,44 @@ export function Profile() { const [isUpdateDialogOpen, setIsUpdateDialogOpen] = useState(false); const [updating, setUpdating] = useState({}); + useEffect(() => { + if (state.UserDetails === undefined) + return; + + let Variant: LabelColorOptions = "default"; + let LabelText: string = ""; + + switch (state.UserDetails.Role) { + case UserPermissions.User: + Variant = "secondary"; + LabelText = "User"; + break; + + case UserPermissions.VerifiedUser: + Variant = "success"; + LabelText = "Verified Track Creator"; + break; + + case UserPermissions.TrackVerifier: + Variant = "done"; + LabelText = "Track Verifier"; + break; + + case UserPermissions.Moderator: + Variant = "accent"; + LabelText = "Moderator"; + break; + + case UserPermissions.Administrator: + Variant = "danger"; + LabelText = "Administrator"; + break; + } + + setVariant(Variant); + setLabelText(LabelText); + }, [state.UserDetails?.Role]) + useEffect(() => { (async () => { const Data = await axios.get("/api/library/me"); @@ -63,6 +104,7 @@ export function Profile() { {state.UserDetails.GlobalName} (@{state.UserDetails.Username}) + diff --git a/src/routes/TrackSubmission.tsx b/src/routes/TrackSubmission.tsx index 80e4970..4c77c77 100644 --- a/src/routes/TrackSubmission.tsx +++ b/src/routes/TrackSubmission.tsx @@ -9,8 +9,8 @@ const formControlStyle = { paddingTop: 3 }; export function TrackSubmission() { const formRef = useRef(null); const [waiting, setWaiting] = useState(false); - const [Key, setKey] = useState("Select a key..."); - const [Scale, setScale] = useState("Select a scale..."); + const [Key, setKey] = useState("Select key..."); + const [Scale, setScale] = useState("Select mode..."); const [GuitarStarterType, setGuitarStarterType] = useState("Select the starter type..."); return ( @@ -61,7 +61,7 @@ export function TrackSubmission() { - Scale + Mode {Scale} @@ -126,7 +126,7 @@ export function TrackSubmission() { console.log(formRef); if (formRef.current == null) - return; + return setWaiting(false); const Name = (formRef.current[0] as HTMLInputElement).value; const ArtistName = (formRef.current[1] as HTMLInputElement).value; @@ -162,14 +162,17 @@ export function TrackSubmission() { }; if (Object.values(B).includes(NaN) || Object.values(B).includes(null) || Object.values(B).includes(undefined)) + { + setWaiting(false); return toast("One or more required fields missing.", { type: "error" }); + } const SongData = await axios.post("/api/drafts/create", B); toast(SongData.data, { type: SongData.status === 200 ? "success" : "error" }); if (SongData.status !== 200) - return; + return setWaiting(false); const MidiRes = await axios.post("/api/drafts/upload/midi", { Data: Buffer.from(await Midi.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID }); toast(MidiRes.status === 200 ? "Uploaded MIDI chart successfully." : MidiRes.data, { type: MidiRes.status === 200 ? "success" : "error" });