rhythia-api 185.0.0 → 187.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.prettierrc.json +6 -6
  2. package/api/addCollectionMap.ts +82 -82
  3. package/api/approveMap.ts +78 -78
  4. package/api/chartPublicStats.ts +32 -32
  5. package/api/createBeatmap.ts +168 -168
  6. package/api/createBeatmapPage.ts +64 -64
  7. package/api/createClan.ts +81 -81
  8. package/api/createCollection.ts +58 -58
  9. package/api/deleteBeatmapPage.ts +77 -77
  10. package/api/deleteCollection.ts +59 -59
  11. package/api/deleteCollectionMap.ts +71 -71
  12. package/api/editAboutMe.ts +91 -91
  13. package/api/editClan.ts +90 -90
  14. package/api/editCollection.ts +77 -77
  15. package/api/editProfile.ts +123 -123
  16. package/api/getAvatarUploadUrl.ts +85 -85
  17. package/api/getBadgedUsers.ts +56 -56
  18. package/api/getBeatmapComments.ts +57 -57
  19. package/api/getBeatmapPage.ts +106 -106
  20. package/api/getBeatmapPageById.ts +99 -99
  21. package/api/getBeatmapStarRating.ts +53 -53
  22. package/api/getBeatmaps.ts +159 -159
  23. package/api/getClan.ts +77 -77
  24. package/api/getCollection.ts +130 -130
  25. package/api/getCollections.ts +132 -130
  26. package/api/getLeaderboard.ts +136 -136
  27. package/api/getMapUploadUrl.ts +93 -93
  28. package/api/getPassToken.ts +55 -55
  29. package/api/getProfile.ts +146 -146
  30. package/api/getPublicStats.ts +180 -180
  31. package/api/getRawStarRating.ts +57 -57
  32. package/api/getScore.ts +85 -85
  33. package/api/getTimestamp.ts +23 -23
  34. package/api/getUserScores.ts +175 -175
  35. package/api/nominateMap.ts +82 -82
  36. package/api/postBeatmapComment.ts +59 -59
  37. package/api/rankMapsArchive.ts +64 -64
  38. package/api/searchUsers.ts +56 -56
  39. package/api/setPasskey.ts +59 -59
  40. package/api/submitScore.ts +433 -433
  41. package/api/updateBeatmapPage.ts +229 -229
  42. package/handleApi.ts +21 -20
  43. package/index.html +2 -2
  44. package/index.ts +867 -866
  45. package/package.json +1 -1
  46. package/types/database.ts +800 -798
  47. package/utils/getUserBySession.ts +48 -48
  48. package/utils/requestUtils.ts +87 -87
  49. package/utils/security.ts +20 -20
  50. package/utils/star-calc/index.ts +72 -72
  51. package/utils/star-calc/osuUtils.ts +53 -53
  52. package/utils/star-calc/sspmParser.ts +398 -398
  53. package/utils/star-calc/sspmv1Parser.ts +165 -165
  54. package/utils/supabase.ts +13 -13
  55. package/utils/test +4 -4
  56. package/utils/validateToken.ts +7 -7
  57. package/vercel.json +12 -12
@@ -1,48 +1,48 @@
1
- import { User } from "@supabase/supabase-js";
2
- import { decryptString } from "./security";
3
- import { supabase } from "./supabase";
4
-
5
- export async function getUserBySession(session: string): Promise<User | null> {
6
- const user = (await supabase.auth.getUser(session)).data.user;
7
-
8
- if (user) {
9
- return user;
10
- }
11
-
12
- try {
13
- console.log("trying legacy token");
14
- const decryptedToken = JSON.parse(decryptString(session)) as {
15
- userId: number;
16
- email: string;
17
- passKey: string;
18
- computerName: string;
19
- };
20
-
21
- for (const key of Object.keys(decryptedToken)) {
22
- if (decryptedToken[key] === undefined || decryptedToken[key] === null) {
23
- return null;
24
- }
25
- }
26
-
27
- console.log(decryptedToken);
28
-
29
- let { data: queryPasskey, error } = await supabase
30
- .from("passkeys")
31
- .select("*,profiles(uid)")
32
- .eq("id", decryptedToken.userId)
33
- .eq("email", decryptedToken.email)
34
- .eq("passkey", decryptedToken.passKey)
35
- .single();
36
-
37
- console.log(queryPasskey);
38
-
39
- if (!queryPasskey) {
40
- return null;
41
- }
42
-
43
- return (await supabase.auth.admin.getUserById(queryPasskey.profiles?.uid!))
44
- .data.user;
45
- } catch (error) {}
46
-
47
- return null;
48
- }
1
+ import { User } from "@supabase/supabase-js";
2
+ import { decryptString } from "./security";
3
+ import { supabase } from "./supabase";
4
+
5
+ export async function getUserBySession(session: string): Promise<User | null> {
6
+ const user = (await supabase.auth.getUser(session)).data.user;
7
+
8
+ if (user) {
9
+ return user;
10
+ }
11
+
12
+ try {
13
+ console.log("trying legacy token");
14
+ const decryptedToken = JSON.parse(decryptString(session)) as {
15
+ userId: number;
16
+ email: string;
17
+ passKey: string;
18
+ computerName: string;
19
+ };
20
+
21
+ for (const key of Object.keys(decryptedToken)) {
22
+ if (decryptedToken[key] === undefined || decryptedToken[key] === null) {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ console.log(decryptedToken);
28
+
29
+ let { data: queryPasskey, error } = await supabase
30
+ .from("passkeys")
31
+ .select("*,profiles(uid)")
32
+ .eq("id", decryptedToken.userId)
33
+ .eq("email", decryptedToken.email)
34
+ .eq("passkey", decryptedToken.passKey)
35
+ .single();
36
+
37
+ console.log(queryPasskey);
38
+
39
+ if (!queryPasskey) {
40
+ return null;
41
+ }
42
+
43
+ return (await supabase.auth.admin.getUserById(queryPasskey.profiles?.uid!))
44
+ .data.user;
45
+ } catch (error) {}
46
+
47
+ return null;
48
+ }
@@ -1,87 +1,87 @@
1
- import { NextResponse } from "next/server";
2
- import { set, ZodObject } from "zod";
3
- import { getUserBySession } from "./getUserBySession";
4
- import { supabase } from "./supabase";
5
-
6
- interface Props<
7
- K = (...args: any[]) => Promise<NextResponse<any>>,
8
- T = ZodObject<any>
9
- > {
10
- request: Request;
11
- schema: { input: T; output: T };
12
- authorization?: Function;
13
- activity: K;
14
- }
15
-
16
- export async function protectedApi({
17
- request,
18
- schema,
19
- authorization,
20
- activity,
21
- }: Props) {
22
- try {
23
- const toParse = await request.json();
24
- const data = schema.input.parse(toParse);
25
-
26
- const dataClone = structuredClone(data);
27
- if (dataClone) {
28
- if (dataClone["token"]) {
29
- dataClone["token"] = "********";
30
- }
31
- Object.keys(dataClone).forEach((key) => {
32
- console.log("KEY: ", key, dataClone[key]);
33
- if (key == "data") {
34
- try {
35
- Object.keys(dataClone[key]).forEach((key2) => {
36
- console.log("KEY2: ", key2, dataClone[key][key2]);
37
- });
38
- } catch (error) {}
39
- }
40
- });
41
- }
42
-
43
- setActivity(data);
44
- if (authorization) {
45
- const authorizationResponse = await authorization(data);
46
- if (authorizationResponse) {
47
- return authorizationResponse;
48
- }
49
- }
50
- return await activity(data, request);
51
- } catch (error) {
52
- return NextResponse.json({ error: error.toString() }, { status: 400 });
53
- }
54
- }
55
-
56
- export async function setActivity(data: Record<string, any>) {
57
- if (data.session) {
58
- const user = (await supabase.auth.getUser(data.session)).data.user;
59
- if (user) {
60
- await supabase.from("profileActivities").upsert({
61
- uid: user.id,
62
- last_activity: Date.now(),
63
- });
64
- }
65
- }
66
- }
67
-
68
- export async function validUser(data) {
69
- if (!data.session) {
70
- return NextResponse.json(
71
- {
72
- error: "Session is missing",
73
- },
74
- { status: 501 }
75
- );
76
- }
77
-
78
- const user = await getUserBySession(data.session);
79
- if (!user) {
80
- return NextResponse.json(
81
- {
82
- error: "Invalid user session",
83
- },
84
- { status: 400 }
85
- );
86
- }
87
- }
1
+ import { NextResponse } from "next/server";
2
+ import { set, ZodObject } from "zod";
3
+ import { getUserBySession } from "./getUserBySession";
4
+ import { supabase } from "./supabase";
5
+
6
+ interface Props<
7
+ K = (...args: any[]) => Promise<NextResponse<any>>,
8
+ T = ZodObject<any>
9
+ > {
10
+ request: Request;
11
+ schema: { input: T; output: T };
12
+ authorization?: Function;
13
+ activity: K;
14
+ }
15
+
16
+ export async function protectedApi({
17
+ request,
18
+ schema,
19
+ authorization,
20
+ activity,
21
+ }: Props) {
22
+ try {
23
+ const toParse = await request.json();
24
+ const data = schema.input.parse(toParse);
25
+
26
+ const dataClone = structuredClone(data);
27
+ if (dataClone) {
28
+ if (dataClone["token"]) {
29
+ dataClone["token"] = "********";
30
+ }
31
+ Object.keys(dataClone).forEach((key) => {
32
+ console.log("KEY: ", key, dataClone[key]);
33
+ if (key == "data") {
34
+ try {
35
+ Object.keys(dataClone[key]).forEach((key2) => {
36
+ console.log("KEY2: ", key2, dataClone[key][key2]);
37
+ });
38
+ } catch (error) {}
39
+ }
40
+ });
41
+ }
42
+
43
+ setActivity(data);
44
+ if (authorization) {
45
+ const authorizationResponse = await authorization(data);
46
+ if (authorizationResponse) {
47
+ return authorizationResponse;
48
+ }
49
+ }
50
+ return await activity(data, request);
51
+ } catch (error) {
52
+ return NextResponse.json({ error: error.toString() }, { status: 400 });
53
+ }
54
+ }
55
+
56
+ export async function setActivity(data: Record<string, any>) {
57
+ if (data.session) {
58
+ const user = (await supabase.auth.getUser(data.session)).data.user;
59
+ if (user) {
60
+ await supabase.from("profileActivities").upsert({
61
+ uid: user.id,
62
+ last_activity: Date.now(),
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ export async function validUser(data) {
69
+ if (!data.session) {
70
+ return NextResponse.json(
71
+ {
72
+ error: "Session is missing",
73
+ },
74
+ { status: 501 }
75
+ );
76
+ }
77
+
78
+ const user = await getUserBySession(data.session);
79
+ if (!user) {
80
+ return NextResponse.json(
81
+ {
82
+ error: "Invalid user session",
83
+ },
84
+ { status: 400 }
85
+ );
86
+ }
87
+ }
package/utils/security.ts CHANGED
@@ -1,20 +1,20 @@
1
- import { xchacha20poly1305 } from "@noble/ciphers/chacha";
2
- import { managedNonce } from "@noble/ciphers/webcrypto";
3
- import {
4
- bytesToHex,
5
- bytesToUtf8,
6
- hexToBytes,
7
- utf8ToBytes,
8
- } from "@noble/ciphers/utils";
9
- const key = hexToBytes(process.env.TOKEN_SECRET || "");
10
- const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
11
-
12
- export function encryptString(str: string) {
13
- const data = utf8ToBytes(str);
14
- return bytesToHex(chacha.encrypt(data));
15
- }
16
-
17
- export function decryptString(str: string) {
18
- const data = hexToBytes(str);
19
- return bytesToUtf8(chacha.decrypt(data));
20
- }
1
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
2
+ import { managedNonce } from "@noble/ciphers/webcrypto";
3
+ import {
4
+ bytesToHex,
5
+ bytesToUtf8,
6
+ hexToBytes,
7
+ utf8ToBytes,
8
+ } from "@noble/ciphers/utils";
9
+ const key = hexToBytes(process.env.TOKEN_SECRET || "");
10
+ const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
11
+
12
+ export function encryptString(str: string) {
13
+ const data = utf8ToBytes(str);
14
+ return bytesToHex(chacha.encrypt(data));
15
+ }
16
+
17
+ export function decryptString(str: string) {
18
+ const data = hexToBytes(str);
19
+ return bytesToUtf8(chacha.decrypt(data));
20
+ }
@@ -1,72 +1,72 @@
1
- import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
2
- import { StandardRuleset } from "osu-standard-stable";
3
- import { sampleMap } from "./osuUtils";
4
- import { SSPMParsedMap } from "./sspmParser";
5
- import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
6
-
7
- export function rateMap(map: SSPMParsedMap) {
8
- let notes = map.markers
9
- .filter((marker) => marker.type === 0)
10
- .map((marker) => ({
11
- time: marker.position,
12
- x: marker.data["field0"].x,
13
- y: marker.data["field0"].y,
14
- }));
15
-
16
- return rate(notes);
17
- }
18
-
19
- export function rateMapNotes(map: [number, number, number][]) {
20
- let notes = map.map((marker) => ({
21
- time: marker[2],
22
- x: marker[0],
23
- y: marker[1],
24
- }));
25
-
26
- return rate(notes);
27
- }
28
-
29
- export function rateMapOld(map: SSPMMap) {
30
- let notes = map.notes.map((marker) => ({
31
- time: marker.position,
32
- x: marker.x,
33
- y: marker.y,
34
- }));
35
-
36
- return rate(notes);
37
- }
38
-
39
- export function rate(
40
- notes: {
41
- time: number;
42
- x: number;
43
- y: number;
44
- }[]
45
- ) {
46
- notes = notes.sort((a, b) => a.time - b.time);
47
- const decoder = new BeatmapDecoder();
48
- const beatmap1 = decoder.decodeFromString(sampleMap);
49
- let i = 0;
50
- while (i < notes.length - 1) {
51
- const note = notes[i];
52
- const nextNote = notes[i + 1];
53
- if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
54
- notes.splice(i + 1, 1);
55
- continue;
56
- }
57
-
58
- const hittable = beatmap1.hitObjects[0].clone();
59
- hittable.startX = Math.round((note.x / 2) * 100);
60
- hittable.startY = Math.round((note.y / 2) * 100);
61
- hittable.startTime = note.time;
62
- beatmap1.hitObjects.push(hittable);
63
- i++;
64
- }
65
- const ruleset = new StandardRuleset();
66
- const mods = ruleset.createModCombination("RX");
67
- const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
68
- const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
69
- return difficultyAttributes.starRating;
70
- }
71
-
72
- const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
1
+ import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
2
+ import { StandardRuleset } from "osu-standard-stable";
3
+ import { sampleMap } from "./osuUtils";
4
+ import { SSPMParsedMap } from "./sspmParser";
5
+ import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
6
+
7
+ export function rateMap(map: SSPMParsedMap) {
8
+ let notes = map.markers
9
+ .filter((marker) => marker.type === 0)
10
+ .map((marker) => ({
11
+ time: marker.position,
12
+ x: marker.data["field0"].x,
13
+ y: marker.data["field0"].y,
14
+ }));
15
+
16
+ return rate(notes);
17
+ }
18
+
19
+ export function rateMapNotes(map: [number, number, number][]) {
20
+ let notes = map.map((marker) => ({
21
+ time: marker[2],
22
+ x: marker[0],
23
+ y: marker[1],
24
+ }));
25
+
26
+ return rate(notes);
27
+ }
28
+
29
+ export function rateMapOld(map: SSPMMap) {
30
+ let notes = map.notes.map((marker) => ({
31
+ time: marker.position,
32
+ x: marker.x,
33
+ y: marker.y,
34
+ }));
35
+
36
+ return rate(notes);
37
+ }
38
+
39
+ export function rate(
40
+ notes: {
41
+ time: number;
42
+ x: number;
43
+ y: number;
44
+ }[]
45
+ ) {
46
+ notes = notes.sort((a, b) => a.time - b.time);
47
+ const decoder = new BeatmapDecoder();
48
+ const beatmap1 = decoder.decodeFromString(sampleMap);
49
+ let i = 0;
50
+ while (i < notes.length - 1) {
51
+ const note = notes[i];
52
+ const nextNote = notes[i + 1];
53
+ if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
54
+ notes.splice(i + 1, 1);
55
+ continue;
56
+ }
57
+
58
+ const hittable = beatmap1.hitObjects[0].clone();
59
+ hittable.startX = Math.round((note.x / 2) * 100);
60
+ hittable.startY = Math.round((note.y / 2) * 100);
61
+ hittable.startTime = note.time;
62
+ beatmap1.hitObjects.push(hittable);
63
+ i++;
64
+ }
65
+ const ruleset = new StandardRuleset();
66
+ const mods = ruleset.createModCombination("RX");
67
+ const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
68
+ const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
69
+ return difficultyAttributes.starRating;
70
+ }
71
+
72
+ const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
@@ -1,53 +1,53 @@
1
- export const sampleMap = `osu file format v14
2
-
3
- [General]
4
- AudioFilename: audio.mp3
5
- AudioLeadIn: 0
6
- PreviewTime: 99664
7
- Countdown: 0
8
- SampleSet: Normal
9
- StackLeniency: 0
10
- Mode: 0
11
- LetterboxInBreaks: 0
12
- UseSkinSprites: 1
13
- SkinPreference:Default
14
- WidescreenStoryboard: 1
15
- SamplesMatchPlaybackRate: 1
16
-
17
- [Editor]
18
-
19
-
20
- [Metadata]
21
- Title:new beginnings
22
- TitleUnicode:new beginnings
23
- Artist:nekodex
24
- ArtistUnicode:nekodex
25
- Creator:pishifat
26
- Version:tutorial
27
- Source:
28
- Tags:
29
- BeatmapID:2116202
30
- BeatmapSetID:1011011
31
-
32
- [Difficulty]
33
- HPDrainRate:5
34
- CircleSize:2
35
- OverallDifficulty:8
36
- ApproachRate:8
37
- SliderMultiplier:1
38
- SliderTickRate:1
39
-
40
- [Events]
41
- //Background and Video events
42
- 0,0,"new-beginnings.jpg",0,0
43
-
44
- [TimingPoints]
45
- -28,461.538461538462,4,1,0,100,1,0
46
-
47
-
48
- [Colours]
49
-
50
-
51
- [HitObjects]
52
- 256,192,24202,1,0,0:0:0:0:
53
- `;
1
+ export const sampleMap = `osu file format v14
2
+
3
+ [General]
4
+ AudioFilename: audio.mp3
5
+ AudioLeadIn: 0
6
+ PreviewTime: 99664
7
+ Countdown: 0
8
+ SampleSet: Normal
9
+ StackLeniency: 0
10
+ Mode: 0
11
+ LetterboxInBreaks: 0
12
+ UseSkinSprites: 1
13
+ SkinPreference:Default
14
+ WidescreenStoryboard: 1
15
+ SamplesMatchPlaybackRate: 1
16
+
17
+ [Editor]
18
+
19
+
20
+ [Metadata]
21
+ Title:new beginnings
22
+ TitleUnicode:new beginnings
23
+ Artist:nekodex
24
+ ArtistUnicode:nekodex
25
+ Creator:pishifat
26
+ Version:tutorial
27
+ Source:
28
+ Tags:
29
+ BeatmapID:2116202
30
+ BeatmapSetID:1011011
31
+
32
+ [Difficulty]
33
+ HPDrainRate:5
34
+ CircleSize:2
35
+ OverallDifficulty:8
36
+ ApproachRate:8
37
+ SliderMultiplier:1
38
+ SliderTickRate:1
39
+
40
+ [Events]
41
+ //Background and Video events
42
+ 0,0,"new-beginnings.jpg",0,0
43
+
44
+ [TimingPoints]
45
+ -28,461.538461538462,4,1,0,100,1,0
46
+
47
+
48
+ [Colours]
49
+
50
+
51
+ [HitObjects]
52
+ 256,192,24202,1,0,0:0:0:0:
53
+ `;