rhythia-api 149.0.0 → 151.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/api/createBeatmap.ts +21 -0
- package/api/getBeatmapPage.ts +3 -0
- package/api/updateBeatmapPage.ts +25 -17
- package/index.ts +4 -3
- package/package.json +2 -2
- package/types/database.ts +19 -9
- package/utils/star-calc/index.ts +27 -22
package/api/createBeatmap.ts
CHANGED
|
@@ -101,6 +101,15 @@ export async function handler({
|
|
|
101
101
|
.toBuffer();
|
|
102
102
|
} catch (error) {}
|
|
103
103
|
|
|
104
|
+
let largeBuffer = Buffer.from([]);
|
|
105
|
+
try {
|
|
106
|
+
largeBuffer = await require("sharp")(parsedData.cover)
|
|
107
|
+
.resize(850)
|
|
108
|
+
.jpeg({ mozjpeg: true })
|
|
109
|
+
.toBuffer();
|
|
110
|
+
} catch (error) {}
|
|
111
|
+
|
|
112
|
+
// Images
|
|
104
113
|
const command = new PutObjectCommand({
|
|
105
114
|
Bucket: "rhthia-avatars",
|
|
106
115
|
Key: imgkey,
|
|
@@ -109,6 +118,17 @@ export async function handler({
|
|
|
109
118
|
});
|
|
110
119
|
|
|
111
120
|
await s3Client.send(command);
|
|
121
|
+
|
|
122
|
+
const command2 = new PutObjectCommand({
|
|
123
|
+
Bucket: "rhthia-avatars",
|
|
124
|
+
Key: imgkey + "large",
|
|
125
|
+
Body: largeBuffer,
|
|
126
|
+
ContentType: "image/jpeg",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await s3Client.send(command2);
|
|
130
|
+
// Images End
|
|
131
|
+
|
|
112
132
|
const markers = parsedData.markers.sort((a, b) => a.position - b.position);
|
|
113
133
|
|
|
114
134
|
const upserted = await supabase.from("beatmaps").upsert({
|
|
@@ -120,6 +140,7 @@ export async function handler({
|
|
|
120
140
|
length: markers[markers.length - 1].position,
|
|
121
141
|
beatmapFile: url,
|
|
122
142
|
image: `https://static.rhythia.com/${imgkey}`,
|
|
143
|
+
imageLarge: `https://static.rhythia.com/${imgkey}large`,
|
|
123
144
|
starRating: rateMap(parsedData),
|
|
124
145
|
});
|
|
125
146
|
|
package/api/getBeatmapPage.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const Schema = {
|
|
|
23
23
|
ranked: z.boolean().nullable().optional(),
|
|
24
24
|
beatmapFile: z.string().nullable().optional(),
|
|
25
25
|
image: z.string().nullable().optional(),
|
|
26
|
+
imageLarge: z.string().nullable().optional(),
|
|
26
27
|
starRating: z.number().nullable().optional(),
|
|
27
28
|
owner: z.number().nullable().optional(),
|
|
28
29
|
ownerUsername: z.string().nullable().optional(),
|
|
@@ -60,6 +61,7 @@ export async function handler(
|
|
|
60
61
|
ranked,
|
|
61
62
|
beatmapFile,
|
|
62
63
|
image,
|
|
64
|
+
imageLarge,
|
|
63
65
|
starRating,
|
|
64
66
|
difficulty,
|
|
65
67
|
noteCount,
|
|
@@ -87,6 +89,7 @@ export async function handler(
|
|
|
87
89
|
ranked: beatmapPage.beatmaps?.ranked,
|
|
88
90
|
beatmapFile: beatmapPage.beatmaps?.beatmapFile,
|
|
89
91
|
image: beatmapPage.beatmaps?.image,
|
|
92
|
+
imageLarge: beatmapPage.beatmaps?.imageLarge,
|
|
90
93
|
starRating: beatmapPage.beatmaps?.starRating,
|
|
91
94
|
owner: beatmapPage.owner,
|
|
92
95
|
ownerUsername: beatmapPage.profiles?.username,
|
package/api/updateBeatmapPage.ts
CHANGED
|
@@ -7,9 +7,9 @@ export const Schema = {
|
|
|
7
7
|
input: z.strictObject({
|
|
8
8
|
session: z.string(),
|
|
9
9
|
id: z.number(),
|
|
10
|
-
beatmapHash: z.string(),
|
|
11
|
-
tags: z.string(),
|
|
12
|
-
description: z.string(),
|
|
10
|
+
beatmapHash: z.string().optional(),
|
|
11
|
+
tags: z.string().optional(),
|
|
12
|
+
description: z.string().optional(),
|
|
13
13
|
}),
|
|
14
14
|
output: z.strictObject({
|
|
15
15
|
error: z.string().optional(),
|
|
@@ -47,14 +47,17 @@ export async function handler({
|
|
|
47
47
|
.eq("id", id)
|
|
48
48
|
.single();
|
|
49
49
|
|
|
50
|
+
if (!userData) return NextResponse.json({ error: "No user." });
|
|
51
|
+
|
|
50
52
|
let { data: beatmapData, error: bmPageError } = await supabase
|
|
51
53
|
.from("beatmaps")
|
|
52
54
|
.select("*")
|
|
53
|
-
.eq("beatmapHash", beatmapHash)
|
|
55
|
+
.eq("beatmapHash", beatmapHash || "")
|
|
54
56
|
.single();
|
|
55
57
|
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
+
if (!beatmapData && beatmapHash) {
|
|
59
|
+
return NextResponse.json({ error: "No beatmap." });
|
|
60
|
+
}
|
|
58
61
|
|
|
59
62
|
if (userData.id !== pageData?.owner)
|
|
60
63
|
return NextResponse.json({ error: "Non-authz user." });
|
|
@@ -62,19 +65,24 @@ export async function handler({
|
|
|
62
65
|
if (pageData?.status !== "UNRANKED")
|
|
63
66
|
return NextResponse.json({ error: "Only unranked maps can be updated" });
|
|
64
67
|
|
|
68
|
+
const upsertPayload = {
|
|
69
|
+
id,
|
|
70
|
+
genre: "",
|
|
71
|
+
status: "UNRANKED",
|
|
72
|
+
owner: userData.id,
|
|
73
|
+
description: description ? description : pageData.description,
|
|
74
|
+
tags: tags ? tags : pageData.tags,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (beatmapHash && beatmapData) {
|
|
78
|
+
upsertPayload["title"] = beatmapData.title;
|
|
79
|
+
upsertPayload["latestBeatmapHash"] = beatmapHash;
|
|
80
|
+
upsertPayload["nominations"] = [];
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
const upserted = await supabase
|
|
66
84
|
.from("beatmapPages")
|
|
67
|
-
.upsert(
|
|
68
|
-
id,
|
|
69
|
-
latestBeatmapHash: beatmapHash,
|
|
70
|
-
genre: "",
|
|
71
|
-
title: beatmapData.title,
|
|
72
|
-
status: "UNRANKED",
|
|
73
|
-
owner: userData.id,
|
|
74
|
-
description,
|
|
75
|
-
tags,
|
|
76
|
-
nominations: [],
|
|
77
|
-
})
|
|
85
|
+
.upsert(upsertPayload)
|
|
78
86
|
.select("*")
|
|
79
87
|
.single();
|
|
80
88
|
|
package/index.ts
CHANGED
|
@@ -198,6 +198,7 @@ export const Schema = {
|
|
|
198
198
|
ranked: z.boolean().nullable().optional(),
|
|
199
199
|
beatmapFile: z.string().nullable().optional(),
|
|
200
200
|
image: z.string().nullable().optional(),
|
|
201
|
+
imageLarge: z.string().nullable().optional(),
|
|
201
202
|
starRating: z.number().nullable().optional(),
|
|
202
203
|
owner: z.number().nullable().optional(),
|
|
203
204
|
ownerUsername: z.string().nullable().optional(),
|
|
@@ -593,9 +594,9 @@ export const Schema = {
|
|
|
593
594
|
input: z.strictObject({
|
|
594
595
|
session: z.string(),
|
|
595
596
|
id: z.number(),
|
|
596
|
-
beatmapHash: z.string(),
|
|
597
|
-
tags: z.string(),
|
|
598
|
-
description: z.string(),
|
|
597
|
+
beatmapHash: z.string().optional(),
|
|
598
|
+
tags: z.string().optional(),
|
|
599
|
+
description: z.string().optional(),
|
|
599
600
|
}),
|
|
600
601
|
output: z.strictObject({
|
|
601
602
|
error: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "151.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update": "bun ./scripts/update.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"osu-standard-stable": "^5.0.0",
|
|
39
39
|
"sharp": "^0.33.5",
|
|
40
40
|
"simple-git": "^3.25.0",
|
|
41
|
-
"supabase": "^1.
|
|
41
|
+
"supabase": "^1.207.9",
|
|
42
42
|
"tsx": "^4.17.0",
|
|
43
43
|
"utf-8-validate": "^6.0.4",
|
|
44
44
|
"zod": "^3.23.8"
|
package/types/database.ts
CHANGED
|
@@ -109,6 +109,7 @@ export type Database = {
|
|
|
109
109
|
created_at: string
|
|
110
110
|
difficulty: number | null
|
|
111
111
|
image: string | null
|
|
112
|
+
imageLarge: string | null
|
|
112
113
|
length: number | null
|
|
113
114
|
noteCount: number | null
|
|
114
115
|
playcount: number | null
|
|
@@ -122,6 +123,7 @@ export type Database = {
|
|
|
122
123
|
created_at?: string
|
|
123
124
|
difficulty?: number | null
|
|
124
125
|
image?: string | null
|
|
126
|
+
imageLarge?: string | null
|
|
125
127
|
length?: number | null
|
|
126
128
|
noteCount?: number | null
|
|
127
129
|
playcount?: number | null
|
|
@@ -135,6 +137,7 @@ export type Database = {
|
|
|
135
137
|
created_at?: string
|
|
136
138
|
difficulty?: number | null
|
|
137
139
|
image?: string | null
|
|
140
|
+
imageLarge?: string | null
|
|
138
141
|
length?: number | null
|
|
139
142
|
noteCount?: number | null
|
|
140
143
|
playcount?: number | null
|
|
@@ -202,15 +205,7 @@ export type Database = {
|
|
|
202
205
|
username?: string | null
|
|
203
206
|
verified?: boolean | null
|
|
204
207
|
}
|
|
205
|
-
Relationships: [
|
|
206
|
-
{
|
|
207
|
-
foreignKeyName: "profiles_uid_fkey"
|
|
208
|
-
columns: ["uid"]
|
|
209
|
-
isOneToOne: true
|
|
210
|
-
referencedRelation: "users"
|
|
211
|
-
referencedColumns: ["id"]
|
|
212
|
-
},
|
|
213
|
-
]
|
|
208
|
+
Relationships: []
|
|
214
209
|
}
|
|
215
210
|
scores: {
|
|
216
211
|
Row: {
|
|
@@ -363,3 +358,18 @@ export type Enums<
|
|
|
363
358
|
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
|
|
364
359
|
? PublicSchema["Enums"][PublicEnumNameOrOptions]
|
|
365
360
|
: never
|
|
361
|
+
|
|
362
|
+
export type CompositeTypes<
|
|
363
|
+
PublicCompositeTypeNameOrOptions extends
|
|
364
|
+
| keyof PublicSchema["CompositeTypes"]
|
|
365
|
+
| { schema: keyof Database },
|
|
366
|
+
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
|
367
|
+
schema: keyof Database
|
|
368
|
+
}
|
|
369
|
+
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
|
370
|
+
: never = never,
|
|
371
|
+
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
|
|
372
|
+
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
|
373
|
+
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
|
|
374
|
+
? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
|
|
375
|
+
: never
|
package/utils/star-calc/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BeatmapDecoder } from "osu-parsers";
|
|
1
|
+
import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
|
|
2
2
|
import { StandardRuleset } from "osu-standard-stable";
|
|
3
3
|
import { sampleMap } from "./osuUtils";
|
|
4
4
|
import { SSPMParsedMap } from "./sspmParser";
|
|
@@ -18,9 +18,6 @@ export function calculatePerformancePoints(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function rateMap(map: SSPMParsedMap) {
|
|
21
|
-
const decoder = new BeatmapDecoder();
|
|
22
|
-
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
23
|
-
|
|
24
21
|
let notes = map.markers
|
|
25
22
|
.filter((marker) => marker.type === 0)
|
|
26
23
|
.map((marker) => ({
|
|
@@ -29,44 +26,52 @@ export function rateMap(map: SSPMParsedMap) {
|
|
|
29
26
|
y: marker.data["field0"].y,
|
|
30
27
|
}));
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
for (const note of notes) {
|
|
35
|
-
const hittable = beatmap1.hitObjects[0].clone();
|
|
36
|
-
hittable.startX = Math.round((note.x / 2) * 100);
|
|
37
|
-
hittable.startY = Math.round((note.y / 2) * 100);
|
|
38
|
-
hittable.startTime = note.time;
|
|
39
|
-
beatmap1.hitObjects.push(hittable);
|
|
40
|
-
}
|
|
41
|
-
const ruleset = new StandardRuleset();
|
|
42
|
-
const mods = ruleset.createModCombination("RX");
|
|
43
|
-
const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
|
|
44
|
-
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
45
|
-
return difficultyAttributes.starRating;
|
|
29
|
+
return rate(notes);
|
|
46
30
|
}
|
|
47
31
|
|
|
48
32
|
export function rateMapOld(map: SSPMMap) {
|
|
49
|
-
const decoder = new BeatmapDecoder();
|
|
50
|
-
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
51
|
-
|
|
52
33
|
let notes = map.notes.map((marker) => ({
|
|
53
34
|
time: marker.position,
|
|
54
35
|
x: marker.x,
|
|
55
36
|
y: marker.y,
|
|
56
37
|
}));
|
|
57
38
|
|
|
39
|
+
return rate(notes);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function rate(
|
|
43
|
+
notes: {
|
|
44
|
+
time: number;
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
}[]
|
|
48
|
+
) {
|
|
58
49
|
notes = notes.sort((a, b) => a.time - b.time);
|
|
50
|
+
const decoder = new BeatmapDecoder();
|
|
51
|
+
const encoder = new BeatmapEncoder();
|
|
52
|
+
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < notes.length - 1) {
|
|
55
|
+
const note = notes[i];
|
|
56
|
+
const nextNote = notes[i + 1];
|
|
57
|
+
if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
|
|
58
|
+
notes.splice(i + 1, 1);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
for (const note of notes) {
|
|
61
62
|
const hittable = beatmap1.hitObjects[0].clone();
|
|
62
63
|
hittable.startX = Math.round((note.x / 2) * 100);
|
|
63
64
|
hittable.startY = Math.round((note.y / 2) * 100);
|
|
64
65
|
hittable.startTime = note.time;
|
|
65
66
|
beatmap1.hitObjects.push(hittable);
|
|
67
|
+
i++;
|
|
66
68
|
}
|
|
67
69
|
const ruleset = new StandardRuleset();
|
|
68
70
|
const mods = ruleset.createModCombination("RX");
|
|
69
71
|
const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
|
|
70
72
|
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
73
|
+
encoder.encodeToPath("./map.osu", beatmap1);
|
|
71
74
|
return difficultyAttributes.starRating;
|
|
72
75
|
}
|
|
76
|
+
|
|
77
|
+
const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
|