rhythia-api 182.0.0 → 185.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.
- package/.prettierrc.json +6 -6
- package/api/addCollectionMap.ts +82 -82
- package/api/approveMap.ts +78 -78
- package/api/chartPublicStats.ts +32 -32
- package/api/createBeatmap.ts +168 -156
- package/api/createBeatmapPage.ts +64 -64
- package/api/createClan.ts +81 -81
- package/api/createCollection.ts +58 -58
- package/api/deleteBeatmapPage.ts +77 -72
- package/api/deleteCollection.ts +59 -59
- package/api/deleteCollectionMap.ts +71 -71
- package/api/editAboutMe.ts +91 -91
- package/api/editClan.ts +90 -90
- package/api/editCollection.ts +77 -77
- package/api/editProfile.ts +123 -123
- package/api/getAvatarUploadUrl.ts +85 -85
- package/api/getBadgedUsers.ts +56 -56
- package/api/getBeatmapComments.ts +57 -57
- package/api/getBeatmapPage.ts +106 -106
- package/api/getBeatmapPageById.ts +99 -99
- package/api/getBeatmapStarRating.ts +53 -53
- package/api/getBeatmaps.ts +159 -159
- package/api/getClan.ts +77 -77
- package/api/getCollection.ts +130 -130
- package/api/getCollections.ts +130 -126
- package/api/getLeaderboard.ts +136 -136
- package/api/getMapUploadUrl.ts +93 -93
- package/api/getPassToken.ts +55 -55
- package/api/getProfile.ts +146 -146
- package/api/getPublicStats.ts +180 -180
- package/api/getRawStarRating.ts +57 -57
- package/api/getScore.ts +85 -85
- package/api/getTimestamp.ts +23 -23
- package/api/getUserScores.ts +175 -175
- package/api/nominateMap.ts +82 -69
- package/api/postBeatmapComment.ts +59 -59
- package/api/rankMapsArchive.ts +64 -64
- package/api/searchUsers.ts +56 -56
- package/api/setPasskey.ts +59 -59
- package/api/submitScore.ts +433 -433
- package/api/updateBeatmapPage.ts +229 -229
- package/handleApi.ts +20 -20
- package/index.html +2 -2
- package/index.ts +866 -864
- package/package.json +2 -2
- package/types/database.ts +114 -1
- package/utils/getUserBySession.ts +48 -48
- package/utils/requestUtils.ts +87 -87
- package/utils/security.ts +20 -20
- package/utils/star-calc/index.ts +72 -72
- package/utils/star-calc/osuUtils.ts +53 -53
- package/utils/star-calc/sspmParser.ts +398 -398
- package/utils/star-calc/sspmv1Parser.ts +165 -165
- package/utils/supabase.ts +13 -13
- package/utils/test +4 -4
- package/utils/validateToken.ts +7 -7
- package/vercel.json +12 -12
- package/package-lock.json +0 -8913
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "185.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"author": "online-contributors",
|
|
6
6
|
"scripts": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"osu-standard-stable": "^5.0.0",
|
|
42
42
|
"sharp": "^0.33.5",
|
|
43
43
|
"simple-git": "^3.25.0",
|
|
44
|
-
"supabase": "^2.
|
|
44
|
+
"supabase": "^2.9.6",
|
|
45
45
|
"tsx": "^4.17.0",
|
|
46
46
|
"utf-8-validate": "^6.0.4",
|
|
47
47
|
"validator": "^13.12.0",
|
package/types/database.ts
CHANGED
|
@@ -256,18 +256,21 @@ export type Database = {
|
|
|
256
256
|
collection: number
|
|
257
257
|
created_at: string
|
|
258
258
|
id: number
|
|
259
|
+
sort: number
|
|
259
260
|
}
|
|
260
261
|
Insert: {
|
|
261
262
|
beatmapPage?: number | null
|
|
262
263
|
collection: number
|
|
263
264
|
created_at?: string
|
|
264
265
|
id?: number
|
|
266
|
+
sort?: number
|
|
265
267
|
}
|
|
266
268
|
Update: {
|
|
267
269
|
beatmapPage?: number | null
|
|
268
270
|
collection?: number
|
|
269
271
|
created_at?: string
|
|
270
272
|
id?: number
|
|
273
|
+
sort?: number
|
|
271
274
|
}
|
|
272
275
|
Relationships: [
|
|
273
276
|
{
|
|
@@ -541,10 +544,120 @@ export type Database = {
|
|
|
541
544
|
total_pages: number
|
|
542
545
|
}[]
|
|
543
546
|
}
|
|
544
|
-
get_collections_v2:
|
|
547
|
+
get_collections_v2:
|
|
548
|
+
| {
|
|
549
|
+
Args: {
|
|
550
|
+
page_number?: number
|
|
551
|
+
items_per_page?: number
|
|
552
|
+
}
|
|
553
|
+
Returns: {
|
|
554
|
+
id: number
|
|
555
|
+
title: string
|
|
556
|
+
description: string
|
|
557
|
+
created_at: string
|
|
558
|
+
owner: number
|
|
559
|
+
owner_username: string
|
|
560
|
+
owner_avatar_url: string
|
|
561
|
+
beatmap_count: number
|
|
562
|
+
star1: number
|
|
563
|
+
star2: number
|
|
564
|
+
star3: number
|
|
565
|
+
star4: number
|
|
566
|
+
star5: number
|
|
567
|
+
star6: number
|
|
568
|
+
star7: number
|
|
569
|
+
star8: number
|
|
570
|
+
star9: number
|
|
571
|
+
star10: number
|
|
572
|
+
star11: number
|
|
573
|
+
star12: number
|
|
574
|
+
star13: number
|
|
575
|
+
star14: number
|
|
576
|
+
star15: number
|
|
577
|
+
star16: number
|
|
578
|
+
star17: number
|
|
579
|
+
star18: number
|
|
580
|
+
total_pages: number
|
|
581
|
+
}[]
|
|
582
|
+
}
|
|
583
|
+
| {
|
|
584
|
+
Args: {
|
|
585
|
+
page_number?: number
|
|
586
|
+
items_per_page?: number
|
|
587
|
+
owner_filter?: number
|
|
588
|
+
}
|
|
589
|
+
Returns: {
|
|
590
|
+
id: number
|
|
591
|
+
title: string
|
|
592
|
+
description: string
|
|
593
|
+
created_at: string
|
|
594
|
+
owner: number
|
|
595
|
+
owner_username: string
|
|
596
|
+
owner_avatar_url: string
|
|
597
|
+
beatmap_count: number
|
|
598
|
+
star1: number
|
|
599
|
+
star2: number
|
|
600
|
+
star3: number
|
|
601
|
+
star4: number
|
|
602
|
+
star5: number
|
|
603
|
+
star6: number
|
|
604
|
+
star7: number
|
|
605
|
+
star8: number
|
|
606
|
+
star9: number
|
|
607
|
+
star10: number
|
|
608
|
+
star11: number
|
|
609
|
+
star12: number
|
|
610
|
+
star13: number
|
|
611
|
+
star14: number
|
|
612
|
+
star15: number
|
|
613
|
+
star16: number
|
|
614
|
+
star17: number
|
|
615
|
+
star18: number
|
|
616
|
+
total_pages: number
|
|
617
|
+
}[]
|
|
618
|
+
}
|
|
619
|
+
get_collections_v3: {
|
|
545
620
|
Args: {
|
|
546
621
|
page_number?: number
|
|
547
622
|
items_per_page?: number
|
|
623
|
+
owner_filter?: number
|
|
624
|
+
}
|
|
625
|
+
Returns: {
|
|
626
|
+
id: number
|
|
627
|
+
title: string
|
|
628
|
+
description: string
|
|
629
|
+
created_at: string
|
|
630
|
+
owner: number
|
|
631
|
+
owner_username: string
|
|
632
|
+
owner_avatar_url: string
|
|
633
|
+
beatmap_count: number
|
|
634
|
+
star1: number
|
|
635
|
+
star2: number
|
|
636
|
+
star3: number
|
|
637
|
+
star4: number
|
|
638
|
+
star5: number
|
|
639
|
+
star6: number
|
|
640
|
+
star7: number
|
|
641
|
+
star8: number
|
|
642
|
+
star9: number
|
|
643
|
+
star10: number
|
|
644
|
+
star11: number
|
|
645
|
+
star12: number
|
|
646
|
+
star13: number
|
|
647
|
+
star14: number
|
|
648
|
+
star15: number
|
|
649
|
+
star16: number
|
|
650
|
+
star17: number
|
|
651
|
+
star18: number
|
|
652
|
+
total_pages: number
|
|
653
|
+
}[]
|
|
654
|
+
}
|
|
655
|
+
get_collections_v4: {
|
|
656
|
+
Args: {
|
|
657
|
+
page_number?: number
|
|
658
|
+
items_per_page?: number
|
|
659
|
+
owner_filter?: number
|
|
660
|
+
search_query?: string
|
|
548
661
|
}
|
|
549
662
|
Returns: {
|
|
550
663
|
id: number
|
|
@@ -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
|
+
}
|
package/utils/requestUtils.ts
CHANGED
|
@@ -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
|
+
}
|
package/utils/star-calc/index.ts
CHANGED
|
@@ -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);
|