Compare commits
2 Commits
f303794f5c
...
970ab22e2f
Author | SHA1 | Date | |
---|---|---|---|
970ab22e2f | |||
d56a85c342 |
16
projects/backend/src/controller/image.controller.ts
Normal file
16
projects/backend/src/controller/image.controller.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Controller, Get } from "elysia-decorators";
|
||||||
|
import { t } from "elysia";
|
||||||
|
import { ImageService } from "../service/image.service";
|
||||||
|
|
||||||
|
@Controller("/image")
|
||||||
|
export default class ImageController {
|
||||||
|
@Get("/player/:id", {
|
||||||
|
config: {},
|
||||||
|
params: t.Object({
|
||||||
|
id: t.String({ required: true }),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
public async getOpenGraphImage({ params: { id } }: { params: { id: string } }) {
|
||||||
|
return await ImageService.generateOpenGraphImage(id);
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ import { PlayerDocument, PlayerModel } from "./model/player";
|
|||||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||||
import { delay } from "@ssr/common/utils/utils";
|
import { delay } from "@ssr/common/utils/utils";
|
||||||
import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket";
|
import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket";
|
||||||
|
import ImageController from "./controller/image.controller";
|
||||||
|
|
||||||
// Load .env file
|
// Load .env file
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
@ -151,7 +152,7 @@ app.use(
|
|||||||
*/
|
*/
|
||||||
app.use(
|
app.use(
|
||||||
decorators({
|
decorators({
|
||||||
controllers: [AppController, PlayerController],
|
controllers: [AppController, PlayerController, ImageController],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
64
projects/backend/src/service/image.service.tsx
Normal file
64
projects/backend/src/service/image.service.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { ImageResponse } from "@vercel/og";
|
||||||
|
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||||
|
import { formatNumberWithCommas, formatPp } from "website/src/common/number-utils";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export class ImageService {
|
||||||
|
/**
|
||||||
|
* Generates the OpenGraph image for the player
|
||||||
|
*
|
||||||
|
* @param id the player's id
|
||||||
|
*/
|
||||||
|
public static async generateOpenGraphImage(id: string) {
|
||||||
|
const player = await scoresaberService.lookupPlayer(id);
|
||||||
|
if (player == undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div tw="w-full h-full flex flex-col text-white bg-black text-3xl p-3 justify-center items-center">
|
||||||
|
<img src={player.profilePicture} width={256} height={256} alt="Player's Avatar" tw="rounded-full" />
|
||||||
|
<div tw="flex flex-col pl-3 items-center">
|
||||||
|
<p tw="font-bold text-6xl m-0">{player.name}</p>
|
||||||
|
<p tw="text-[#606fff] m-0">{formatPp(player.pp)}pp</p>
|
||||||
|
<div tw="flex">
|
||||||
|
<div tw="flex px-2 justify-center items-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 26"
|
||||||
|
fill="currentColor"
|
||||||
|
style={{
|
||||||
|
width: "33px",
|
||||||
|
height: "33px",
|
||||||
|
paddingRight: "3px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25ZM6.262 6.072a8.25 8.25 0 1 0 10.562-.766 4.5 4.5 0 0 1-1.318 1.357L14.25 7.5l.165.33a.809.809 0 0 1-1.086 1.085l-.604-.302a1.125 1.125 0 0 0-1.298.21l-.132.131c-.439.44-.439 1.152 0 1.591l.296.296c.256.257.622.374.98.314l1.17-.195c.323-.054.654.036.905.245l1.33 1.108c.32.267.46.694.358 1.1a8.7 8.7 0 0 1-2.288 4.04l-.723.724a1.125 1.125 0 0 1-1.298.21l-.153-.076a1.125 1.125 0 0 1-.622-1.006v-1.089c0-.298-.119-.585-.33-.796l-1.347-1.347a1.125 1.125 0 0 1-.21-1.298L9.75 12l-1.64-1.64a6 6 0 0 1-1.676-3.257l-.172-1.03Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p tw="m-0">#{formatNumberWithCommas(player.rank)}</p>
|
||||||
|
</div>
|
||||||
|
<div tw="flex items-center px-2 justify-center items-center">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={`https://ssr.fascinated.cc/assets/flags/${player.country.toLowerCase()}.png`}
|
||||||
|
height={20}
|
||||||
|
alt="Player's Country"
|
||||||
|
/>
|
||||||
|
<p tw="pl-1 m-0">#{formatNumberWithCommas(player.countryRank)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
emoji: "twemoji",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,6 @@ import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
|||||||
import ScoreSaberPlayerToken from "@ssr/common/types/token/scoresaber/score-saber-player-token";
|
import ScoreSaberPlayerToken from "@ssr/common/types/token/scoresaber/score-saber-player-token";
|
||||||
import { InternalServerError } from "../error/internal-server-error";
|
import { InternalServerError } from "../error/internal-server-error";
|
||||||
import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token";
|
import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token";
|
||||||
import { ImageResponse } from "@vercel/og";
|
|
||||||
import { formatNumberWithCommas, formatPp } from "website/src/common/number-utils";
|
|
||||||
import { config } from "website/config";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export class PlayerService {
|
export class PlayerService {
|
||||||
/**
|
/**
|
||||||
@ -159,62 +155,4 @@ export class PlayerService {
|
|||||||
`Updated scores set statistic for "${playerName}"(${playerId}), scores today: ${scores.rankedScores} ranked, ${scores.unrankedScores} unranked`
|
`Updated scores set statistic for "${playerName}"(${playerId}), scores today: ${scores.rankedScores} ranked, ${scores.unrankedScores} unranked`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the OpenGraph image for the player
|
|
||||||
*
|
|
||||||
* @param id the player's id
|
|
||||||
*/
|
|
||||||
public static async generateOpenGraphImage(id: string) {
|
|
||||||
const player = await scoresaberService.lookupPlayer(id);
|
|
||||||
if (player == undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return new ImageResponse(
|
|
||||||
(
|
|
||||||
<div tw="w-full h-full flex flex-col text-white bg-black text-3xl p-3 justify-center items-center">
|
|
||||||
<img src={player.profilePicture} width={256} height={256} alt="Player's Avatar" tw="rounded-full" />
|
|
||||||
<div tw="flex flex-col pl-3 items-center">
|
|
||||||
<p tw="font-bold text-6xl m-0">{player.name}</p>
|
|
||||||
<p tw="text-[#606fff] m-0">{formatPp(player.pp)}pp</p>
|
|
||||||
<div tw="flex">
|
|
||||||
<div tw="flex px-2 justify-center items-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 26"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{
|
|
||||||
width: "33px",
|
|
||||||
height: "33px",
|
|
||||||
paddingRight: "3px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25ZM6.262 6.072a8.25 8.25 0 1 0 10.562-.766 4.5 4.5 0 0 1-1.318 1.357L14.25 7.5l.165.33a.809.809 0 0 1-1.086 1.085l-.604-.302a1.125 1.125 0 0 0-1.298.21l-.132.131c-.439.44-.439 1.152 0 1.591l.296.296c.256.257.622.374.98.314l1.17-.195c.323-.054.654.036.905.245l1.33 1.108c.32.267.46.694.358 1.1a8.7 8.7 0 0 1-2.288 4.04l-.723.724a1.125 1.125 0 0 1-1.298.21l-.153-.076a1.125 1.125 0 0 1-.622-1.006v-1.089c0-.298-.119-.585-.33-.796l-1.347-1.347a1.125 1.125 0 0 1-.21-1.298L9.75 12l-1.64-1.64a6 6 0 0 1-1.676-3.257l-.172-1.03Z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<p tw="m-0">#{formatNumberWithCommas(player.rank)}</p>
|
|
||||||
</div>
|
|
||||||
<div tw="flex items-center px-2 justify-center items-center">
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
|
||||||
src={`${config.siteUrl}/assets/flags/${player.country.toLowerCase()}.png`}
|
|
||||||
height={20}
|
|
||||||
alt="Player's Country"
|
|
||||||
/>
|
|
||||||
<p tw="pl-1 m-0">#{formatNumberWithCommas(player.countryRank)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
emoji: "twemoji",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -97,7 +97,7 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
|
|||||||
title: `ScoreSaber Reloaded - ${player.name}`,
|
title: `ScoreSaber Reloaded - ${player.name}`,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: `${config.siteApi}/player/og/${player.id}`,
|
url: `${config.siteApi}/image/player/${player.id}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user