diff --git a/projects/backend/package.json b/projects/backend/package.json
index dfbcb6e..8f1a9d2 100644
--- a/projects/backend/package.json
+++ b/projects/backend/package.json
@@ -20,6 +20,7 @@
"elysia-decorators": "^1.0.2",
"elysia-helmet": "^2.0.0",
"elysia-rate-limit": "^4.1.0",
+ "ky": "^1.7.2",
"mongoose": "^8.7.0",
"react": "^18.3.1"
},
diff --git a/projects/backend/src/controller/image.controller.ts b/projects/backend/src/controller/image.controller.ts
index 8ed58c6..c94ed3a 100644
--- a/projects/backend/src/controller/image.controller.ts
+++ b/projects/backend/src/controller/image.controller.ts
@@ -10,7 +10,17 @@ export default class ImageController {
id: t.String({ required: true }),
}),
})
- public async getOpenGraphImage({ params: { id } }: { params: { id: string } }) {
+ public async getPlayerImage({ params: { id } }: { params: { id: string } }) {
return await ImageService.generatePlayerImage(id);
}
+
+ @Get("/leaderboard/:id", {
+ config: {},
+ params: t.Object({
+ id: t.String({ required: true }),
+ }),
+ })
+ public async getLeaderboardImage({ params: { id } }: { params: { id: string } }) {
+ return await ImageService.generateLeaderboardImage(id);
+ }
}
diff --git a/projects/backend/src/controller/player.controller.ts b/projects/backend/src/controller/player.controller.ts
index 4bfe294..9e09b0b 100644
--- a/projects/backend/src/controller/player.controller.ts
+++ b/projects/backend/src/controller/player.controller.ts
@@ -51,14 +51,4 @@ export default class PlayerController {
};
}
}
-
- @Get("/og/:id", {
- config: {},
- params: t.Object({
- id: t.String({ required: true }),
- }),
- })
- public async getOpenGraphImage({ params: { id } }: { params: { id: string } }) {
- return await PlayerService.generateOpenGraphImage(id);
- }
}
diff --git a/projects/backend/src/controller/replay.controller.ts b/projects/backend/src/controller/replay.controller.ts
new file mode 100644
index 0000000..fb57984
--- /dev/null
+++ b/projects/backend/src/controller/replay.controller.ts
@@ -0,0 +1,21 @@
+import { Controller, Get } from "elysia-decorators";
+import { t } from "elysia";
+import { ReplayService } from "../service/replay.service";
+
+@Controller("/replay")
+export default class ReplayController {
+ @Get("/:playerId/:leaderboardId", {
+ config: {},
+ params: t.Object({
+ playerId: t.String({ required: true }),
+ leaderboardId: t.String({ required: true }),
+ }),
+ })
+ public async getOpenGraphImage({
+ params: { playerId, leaderboardId },
+ }: {
+ params: { playerId: string; leaderboardId: string };
+ }) {
+ return ReplayService.getReplay(playerId, leaderboardId);
+ }
+}
diff --git a/projects/backend/src/index.ts b/projects/backend/src/index.ts
index 503199d..ed1c7b1 100644
--- a/projects/backend/src/index.ts
+++ b/projects/backend/src/index.ts
@@ -20,6 +20,7 @@ import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import { delay } from "@ssr/common/utils/utils";
import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket";
import ImageController from "./controller/image.controller";
+import ReplayController from "./controller/replay.controller";
// Load .env file
dotenv.config({
@@ -152,7 +153,7 @@ app.use(
*/
app.use(
decorators({
- controllers: [AppController, PlayerController, ImageController],
+ controllers: [AppController, PlayerController, ImageController, ReplayController],
})
);
diff --git a/projects/backend/src/service/image.service.tsx b/projects/backend/src/service/image.service.tsx
index e16661b..58b75d2 100644
--- a/projects/backend/src/service/image.service.tsx
+++ b/projects/backend/src/service/image.service.tsx
@@ -2,6 +2,7 @@ import { ImageResponse } from "@vercel/og";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import React from "react";
import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils";
+import { getDifficultyFromScoreSaberDifficulty } from "website/src/common/scoresaber-utils";
export class ImageService {
/**
@@ -23,7 +24,7 @@ export class ImageService {
background: "radial-gradient(ellipse 60% 60% at 50% -20%, rgba(120,119,198,0.15), rgba(255,255,255,0))",
}}
>
-
+
{player.name}
{formatPp(player.pp)}pp
@@ -67,4 +68,67 @@ export class ImageService { } ); } + + /** + * Generates the OpenGraph image for the player + * + * @param id the player's id + */ + public static async generateLeaderboardImage(id: string) { + const leaderboard = await scoresaberService.lookupLeaderboard(id); + if (leaderboard == undefined) { + return undefined; + } + + const ranked = leaderboard.stars > 0; + + return new ImageResponse( + ( ++ {leaderboard.songName} {leaderboard.songSubName} +
+{leaderboard.stars}
+ ++ {getDifficultyFromScoreSaberDifficulty(leaderboard.difficulty.difficulty)} +
+Mapped by {leaderboard.levelAuthorName}
+