rhythia-api 171.0.0 → 173.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 +1 -1
- package/api/createBeatmapPage.ts +2 -1
- package/api/editProfile.ts +14 -0
- package/api/getLeaderboard.ts +12 -1
- package/api/getRawStarRating.ts +46 -0
- package/api/searchUsers.ts +15 -1
- package/api/submitScore.ts +14 -1
- package/api/updateBeatmapPage.ts +134 -1
- package/index.ts +28 -0
- package/package-lock.json +8913 -8814
- package/package.json +4 -1
- package/types/database.ts +7 -0
- package/utils/star-calc/index.ts +10 -0
package/api/createBeatmap.ts
CHANGED
|
@@ -73,7 +73,7 @@ export async function handler({
|
|
|
73
73
|
return NextResponse.json(
|
|
74
74
|
{
|
|
75
75
|
error:
|
|
76
|
-
"Silenced, restricted or excluded players can't
|
|
76
|
+
"Silenced, restricted or excluded players can't create beatmaps their profile.",
|
|
77
77
|
},
|
|
78
78
|
{ status: 404 }
|
|
79
79
|
);
|
package/api/createBeatmapPage.ts
CHANGED
|
@@ -46,7 +46,7 @@ export async function handler({
|
|
|
46
46
|
return NextResponse.json(
|
|
47
47
|
{
|
|
48
48
|
error:
|
|
49
|
-
"Silenced, restricted or excluded players can't
|
|
49
|
+
"Silenced, restricted or excluded players can't create beatmap pages.",
|
|
50
50
|
},
|
|
51
51
|
{ status: 404 }
|
|
52
52
|
);
|
|
@@ -59,5 +59,6 @@ export async function handler({
|
|
|
59
59
|
})
|
|
60
60
|
.select("*")
|
|
61
61
|
.single();
|
|
62
|
+
|
|
62
63
|
return NextResponse.json({ id: upserted.data?.id });
|
|
63
64
|
}
|
package/api/editProfile.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { protectedApi, validUser } from "../utils/requestUtils";
|
|
|
5
5
|
import { supabase } from "../utils/supabase";
|
|
6
6
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
7
|
import { User } from "@supabase/supabase-js";
|
|
8
|
+
import validator from "validator";
|
|
9
|
+
import removeZeroWidth from "zero-width";
|
|
10
|
+
|
|
8
11
|
export const Schema = {
|
|
9
12
|
input: z.strictObject({
|
|
10
13
|
session: z.string(),
|
|
@@ -49,6 +52,17 @@ export async function handler(
|
|
|
49
52
|
);
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
if (validator.trim(data.data.username || "") !== (data.data.username || "")) {
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{
|
|
58
|
+
error: "Username can't start or end with spaces.",
|
|
59
|
+
},
|
|
60
|
+
{ status: 404 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
data.data.username = removeZeroWidth(data.data.username || "");
|
|
65
|
+
|
|
52
66
|
const user = (await getUserBySession(data.session)) as User;
|
|
53
67
|
|
|
54
68
|
let userData: Database["public"]["Tables"]["profiles"]["Update"];
|
package/api/getLeaderboard.ts
CHANGED
|
@@ -28,6 +28,13 @@ export const Schema = {
|
|
|
28
28
|
skill_points: z.number().nullable(),
|
|
29
29
|
spin_skill_points: z.number().nullable(),
|
|
30
30
|
total_score: z.number().nullable(),
|
|
31
|
+
clans: z
|
|
32
|
+
.object({
|
|
33
|
+
id: z.number(),
|
|
34
|
+
acronym: z.string(),
|
|
35
|
+
})
|
|
36
|
+
.optional()
|
|
37
|
+
.nullable(),
|
|
31
38
|
})
|
|
32
39
|
)
|
|
33
40
|
.optional(),
|
|
@@ -92,7 +99,10 @@ export async function getLeaderboard(
|
|
|
92
99
|
.select("ban", { count: "exact", head: true })
|
|
93
100
|
.neq("ban", "excluded");
|
|
94
101
|
|
|
95
|
-
let query = supabase
|
|
102
|
+
let query = supabase
|
|
103
|
+
.from("profiles")
|
|
104
|
+
.select("*,clans:clan(id, acronym)")
|
|
105
|
+
.neq("ban", "excluded");
|
|
96
106
|
|
|
97
107
|
if (flag) {
|
|
98
108
|
query.eq("flag", flag);
|
|
@@ -120,6 +130,7 @@ export async function getLeaderboard(
|
|
|
120
130
|
spin_skill_points: user.spin_skill_points,
|
|
121
131
|
total_score: user.total_score,
|
|
122
132
|
username: user.username,
|
|
133
|
+
clans: user.clans as any,
|
|
123
134
|
})),
|
|
124
135
|
};
|
|
125
136
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi } from "../utils/requestUtils";
|
|
4
|
+
import { rateMapNotes } from "../utils/star-calc";
|
|
5
|
+
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
session: z.string(),
|
|
9
|
+
rawMap: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
output: z.object({
|
|
12
|
+
error: z.string().optional(),
|
|
13
|
+
beatmap: z
|
|
14
|
+
.object({
|
|
15
|
+
starRating: z.number().nullable().optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional(),
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
22
|
+
return protectedApi({
|
|
23
|
+
request,
|
|
24
|
+
schema: Schema,
|
|
25
|
+
authorization: () => {},
|
|
26
|
+
activity: handler,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handler(
|
|
31
|
+
data: (typeof Schema)["input"]["_type"],
|
|
32
|
+
req: Request
|
|
33
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
34
|
+
const notes = data.rawMap.split(",");
|
|
35
|
+
notes.shift();
|
|
36
|
+
|
|
37
|
+
const rawNotes = notes.map(
|
|
38
|
+
(e) => e.split("|").map((e) => Number(e)) as [number, number, number]
|
|
39
|
+
);
|
|
40
|
+
const starRating = rateMapNotes(rawNotes);
|
|
41
|
+
return NextResponse.json({
|
|
42
|
+
beatmap: {
|
|
43
|
+
starRating,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
package/api/searchUsers.ts
CHANGED
|
@@ -30,13 +30,27 @@ export async function POST(request: Request) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
33
|
+
const { data: exactUser } = await supabase
|
|
34
|
+
.from("profiles")
|
|
35
|
+
.select("id,username")
|
|
36
|
+
.neq("ban", "excluded")
|
|
37
|
+
.eq("computedUsername", data.text.toLocaleLowerCase())
|
|
38
|
+
.single();
|
|
39
|
+
|
|
33
40
|
const { data: searchData, error } = await supabase
|
|
34
41
|
.from("profiles")
|
|
35
42
|
.select("id,username")
|
|
36
43
|
.neq("ban", "excluded")
|
|
37
44
|
.ilike("username", `%${data.text}%`)
|
|
38
45
|
.limit(10);
|
|
46
|
+
|
|
47
|
+
const conjoined = searchData || [];
|
|
48
|
+
|
|
49
|
+
if (exactUser && !conjoined.some((user) => user.id === exactUser?.id)) {
|
|
50
|
+
conjoined.unshift(exactUser);
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
return NextResponse.json({
|
|
40
|
-
results:
|
|
54
|
+
results: conjoined || [],
|
|
41
55
|
});
|
|
42
56
|
}
|
package/api/submitScore.ts
CHANGED
|
@@ -116,6 +116,16 @@ export async function handler({
|
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
console.log(userData);
|
|
119
|
+
|
|
120
|
+
if (userData.ban == "excluded" || userData.ban == "restricted") {
|
|
121
|
+
return NextResponse.json(
|
|
122
|
+
{
|
|
123
|
+
error: "Silenced, restricted or excluded players can't submit scores.",
|
|
124
|
+
},
|
|
125
|
+
{ status: 400 }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
let { data: beatmaps, error } = await supabase
|
|
120
130
|
.from("beatmaps")
|
|
121
131
|
.select("*")
|
|
@@ -386,7 +396,10 @@ export async function postToWebhooks({
|
|
|
386
396
|
mapid: number;
|
|
387
397
|
misses: number;
|
|
388
398
|
}) {
|
|
389
|
-
const webHooks = await supabase
|
|
399
|
+
const webHooks = await supabase
|
|
400
|
+
.from("discordWebhooks")
|
|
401
|
+
.select("*")
|
|
402
|
+
.eq("type", "scores");
|
|
390
403
|
|
|
391
404
|
if (!webHooks.data) return;
|
|
392
405
|
|
package/api/updateBeatmapPage.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { protectedApi, validUser } from "../utils/requestUtils";
|
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
5
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
6
|
import { User } from "@supabase/supabase-js";
|
|
7
|
+
import { calculatePerformancePoints } from "./submitScore";
|
|
7
8
|
|
|
8
9
|
export const Schema = {
|
|
9
10
|
input: z.strictObject({
|
|
@@ -26,7 +27,14 @@ export async function POST(request: Request): Promise<NextResponse> {
|
|
|
26
27
|
activity: handler,
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
export function formatTime(milliseconds: number): string {
|
|
31
|
+
const totalSeconds = Math.floor(milliseconds / 1000);
|
|
32
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
33
|
+
const seconds = totalSeconds % 60;
|
|
34
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds
|
|
35
|
+
.toString()
|
|
36
|
+
.padStart(2, "0")}`;
|
|
37
|
+
}
|
|
30
38
|
export async function handler({
|
|
31
39
|
session,
|
|
32
40
|
beatmapHash,
|
|
@@ -92,5 +100,130 @@ export async function handler({
|
|
|
92
100
|
if (upserted.error?.message.length) {
|
|
93
101
|
return NextResponse.json({ error: upserted.error.message });
|
|
94
102
|
}
|
|
103
|
+
|
|
104
|
+
if (beatmapData?.starRating) {
|
|
105
|
+
postBeatmapToWebhooks({
|
|
106
|
+
username: userData.username || "",
|
|
107
|
+
userid: userData.id,
|
|
108
|
+
avatar: userData.avatar_url || "",
|
|
109
|
+
mapimage: beatmapData?.image || "",
|
|
110
|
+
mapname: beatmapData?.title || "",
|
|
111
|
+
mapid: upserted.data?.id || 0,
|
|
112
|
+
mapDownload: beatmapData?.beatmapFile || "",
|
|
113
|
+
starRating: beatmapData?.starRating || 0,
|
|
114
|
+
length: beatmapData?.length || 0,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
95
118
|
return NextResponse.json({});
|
|
96
119
|
}
|
|
120
|
+
|
|
121
|
+
const beatmapWebhookTemplate: any = {
|
|
122
|
+
content: null,
|
|
123
|
+
embeds: [
|
|
124
|
+
{
|
|
125
|
+
title: "Captain Lou Albano - Do the Mario",
|
|
126
|
+
description: "Beatmap Created",
|
|
127
|
+
url: "https://www.rhythia.com/maps/4469",
|
|
128
|
+
color: 16775930,
|
|
129
|
+
fields: [
|
|
130
|
+
{
|
|
131
|
+
name: "Star Rating",
|
|
132
|
+
value: "12.4*",
|
|
133
|
+
inline: true,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "Length",
|
|
137
|
+
value: "4:24 minutes",
|
|
138
|
+
inline: true,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "Max RP",
|
|
142
|
+
value: "100 RP",
|
|
143
|
+
inline: true,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
author: {
|
|
147
|
+
name: "cunev",
|
|
148
|
+
url: "https://www.rhythia.com/player/0",
|
|
149
|
+
icon_url:
|
|
150
|
+
"https://static.rhythia.com/user-avatar-1735149648551-a2a8cfbe-af5d-46e8-a19a-be2339c1679a",
|
|
151
|
+
},
|
|
152
|
+
footer: {
|
|
153
|
+
text: "Sun, 22 Dec 2024 22:40:17 GMT",
|
|
154
|
+
},
|
|
155
|
+
thumbnail: {
|
|
156
|
+
url: "https://static.rhythia.com/beatmap-img-1735995790136-gen_-_frozy_tomo_-_islands_(kompa_pasi%C3%B3n)large",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
title: "Direct download",
|
|
161
|
+
url: "https://www.rhythia.com/maps/4469",
|
|
162
|
+
color: null,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
attachments: [],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export async function postBeatmapToWebhooks({
|
|
169
|
+
username,
|
|
170
|
+
userid,
|
|
171
|
+
avatar,
|
|
172
|
+
mapimage,
|
|
173
|
+
mapname,
|
|
174
|
+
mapid,
|
|
175
|
+
starRating,
|
|
176
|
+
length,
|
|
177
|
+
mapDownload,
|
|
178
|
+
}: {
|
|
179
|
+
username: string;
|
|
180
|
+
userid: number;
|
|
181
|
+
avatar: string;
|
|
182
|
+
mapimage: string;
|
|
183
|
+
mapname: string;
|
|
184
|
+
mapid: number;
|
|
185
|
+
starRating: number;
|
|
186
|
+
length: number;
|
|
187
|
+
mapDownload: string;
|
|
188
|
+
}) {
|
|
189
|
+
// format length in MM:SS with padding 0
|
|
190
|
+
|
|
191
|
+
const webHooks = await supabase
|
|
192
|
+
.from("discordWebhooks")
|
|
193
|
+
.select("*")
|
|
194
|
+
.eq("type", "maps");
|
|
195
|
+
|
|
196
|
+
if (!webHooks.data) return;
|
|
197
|
+
|
|
198
|
+
for (const webhook of webHooks.data) {
|
|
199
|
+
const webhookUrl = webhook.webhook_link;
|
|
200
|
+
|
|
201
|
+
const mainEmbed = beatmapWebhookTemplate.embeds[0];
|
|
202
|
+
const downloadEmbed = beatmapWebhookTemplate.embeds[1];
|
|
203
|
+
|
|
204
|
+
mainEmbed.title = mapname;
|
|
205
|
+
mainEmbed.url = `https://www.rhythia.com/maps/${mapid}`;
|
|
206
|
+
mainEmbed.fields[0].value = `${Math.round(starRating * 100) / 100}*`;
|
|
207
|
+
mainEmbed.fields[1].value = `${formatTime(length)} minutes`;
|
|
208
|
+
mainEmbed.fields[2].value =
|
|
209
|
+
calculatePerformancePoints(starRating, 1) + " RP";
|
|
210
|
+
mainEmbed.author.name = username;
|
|
211
|
+
mainEmbed.author.url = `https://www.rhythia.com/player/${userid}`;
|
|
212
|
+
mainEmbed.author.icon_url = avatar;
|
|
213
|
+
mainEmbed.thumbnail.url = mapimage;
|
|
214
|
+
if (mapimage.includes("backfill")) {
|
|
215
|
+
mainEmbed.thumbnail.url = "https://www.rhythia.com/unkimg.png";
|
|
216
|
+
}
|
|
217
|
+
mainEmbed.footer.text = new Date().toUTCString();
|
|
218
|
+
|
|
219
|
+
downloadEmbed.url = mapDownload;
|
|
220
|
+
|
|
221
|
+
await fetch(webhookUrl, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
},
|
|
226
|
+
body: JSON.stringify(beatmapWebhookTemplate),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
package/index.ts
CHANGED
|
@@ -429,6 +429,13 @@ export const Schema = {
|
|
|
429
429
|
skill_points: z.number().nullable(),
|
|
430
430
|
spin_skill_points: z.number().nullable(),
|
|
431
431
|
total_score: z.number().nullable(),
|
|
432
|
+
clans: z
|
|
433
|
+
.object({
|
|
434
|
+
id: z.number(),
|
|
435
|
+
acronym: z.string(),
|
|
436
|
+
})
|
|
437
|
+
.optional()
|
|
438
|
+
.nullable(),
|
|
432
439
|
})
|
|
433
440
|
)
|
|
434
441
|
.optional(),
|
|
@@ -576,6 +583,27 @@ import { Schema as GetPublicStats } from "./api/getPublicStats"
|
|
|
576
583
|
export { Schema as SchemaGetPublicStats } from "./api/getPublicStats"
|
|
577
584
|
export const getPublicStats = handleApi({url:"/api/getPublicStats",...GetPublicStats})
|
|
578
585
|
|
|
586
|
+
// ./api/getRawStarRating.ts API
|
|
587
|
+
|
|
588
|
+
/*
|
|
589
|
+
export const Schema = {
|
|
590
|
+
input: z.strictObject({
|
|
591
|
+
session: z.string(),
|
|
592
|
+
rawMap: z.string(),
|
|
593
|
+
}),
|
|
594
|
+
output: z.object({
|
|
595
|
+
error: z.string().optional(),
|
|
596
|
+
beatmap: z
|
|
597
|
+
.object({
|
|
598
|
+
starRating: z.number().nullable().optional(),
|
|
599
|
+
})
|
|
600
|
+
.optional(),
|
|
601
|
+
}),
|
|
602
|
+
};*/
|
|
603
|
+
import { Schema as GetRawStarRating } from "./api/getRawStarRating"
|
|
604
|
+
export { Schema as SchemaGetRawStarRating } from "./api/getRawStarRating"
|
|
605
|
+
export const getRawStarRating = handleApi({url:"/api/getRawStarRating",...GetRawStarRating})
|
|
606
|
+
|
|
579
607
|
// ./api/getScore.ts API
|
|
580
608
|
|
|
581
609
|
/*
|