Compare commits
No commits in common. "3dcf03ce531fb6fd647409045c444db52617cb37" and "ff9408fb8c268cc838e5171ae03887159797b49d" have entirely different histories.
3dcf03ce53
...
ff9408fb8c
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,18 @@
|
|||||||
FROM oven/bun:1.1.30-alpine AS base
|
FROM oven/bun:1.1.30-alpine AS base
|
||||||
|
|
||||||
|
# Install system dependencies for node-canvas
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
cairo-dev \
|
||||||
|
pango-dev \
|
||||||
|
giflib-dev \
|
||||||
|
libjpeg-turbo-dev \
|
||||||
|
freetype-dev \
|
||||||
|
fontconfig-dev \
|
||||||
|
pixman-dev \
|
||||||
|
python3 \
|
||||||
|
pkgconfig
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
FROM base AS depends
|
FROM base AS depends
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -24,7 +37,4 @@ RUN bun --filter '@ssr/common' build
|
|||||||
# Copy the backend project
|
# Copy the backend project
|
||||||
COPY --from=depends /app/projects/backend ./projects/backend
|
COPY --from=depends /app/projects/backend ./projects/backend
|
||||||
|
|
||||||
# Lint before starting
|
|
||||||
RUN bun --filter '@ssr/common' lint
|
|
||||||
|
|
||||||
CMD ["bun", "run", "--filter", "backend", "start"]
|
CMD ["bun", "run", "--filter", "backend", "start"]
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --watch src/index.ts",
|
"dev": "bun run --watch src/index.ts",
|
||||||
"start": "bun run src/index.ts",
|
"start": "bun run src/index.ts"
|
||||||
"lint": "eslint src/**/*.ts"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bogeychan/elysia-etag": "^0.0.6",
|
"@bogeychan/elysia-etag": "^0.0.6",
|
||||||
@ -15,18 +14,15 @@
|
|||||||
"@ssr/common": "workspace:common",
|
"@ssr/common": "workspace:common",
|
||||||
"@tqman/nice-logger": "^1.0.1",
|
"@tqman/nice-logger": "^1.0.1",
|
||||||
"@typegoose/typegoose": "^12.8.0",
|
"@typegoose/typegoose": "^12.8.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.9.0",
|
|
||||||
"@typescript-eslint/parser": "^8.9.0",
|
|
||||||
"@vercel/og": "^0.6.3",
|
"@vercel/og": "^0.6.3",
|
||||||
|
"canvas": "^3.0.0-rc2",
|
||||||
"discord-webhook-node": "^1.1.8",
|
"discord-webhook-node": "^1.1.8",
|
||||||
"elysia": "latest",
|
"elysia": "latest",
|
||||||
"elysia-autoroutes": "^0.5.0",
|
"elysia-autoroutes": "^0.5.0",
|
||||||
"elysia-decorators": "^1.0.2",
|
"elysia-decorators": "^1.0.2",
|
||||||
"elysia-helmet": "^2.0.0",
|
"elysia-helmet": "^2.0.0",
|
||||||
"elysia-rate-limit": "^4.1.0",
|
"elysia-rate-limit": "^4.1.0",
|
||||||
"eslint": "^8.57.1",
|
|
||||||
"extract-colors": "^4.1.0",
|
"extract-colors": "^4.1.0",
|
||||||
"jimp": "^1.6.0",
|
|
||||||
"ky": "^1.7.2",
|
"ky": "^1.7.2",
|
||||||
"mongoose": "^8.7.0",
|
"mongoose": "^8.7.0",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Gets the app version.
|
* Gets the app version.
|
||||||
*/
|
*/
|
||||||
export async function getAppVersion() {
|
export function getAppVersion() {
|
||||||
if (!process.env.APP_VERSION) {
|
if (!process.env.APP_VERSION) {
|
||||||
const packageJson = await import("../../package.json");
|
const packageJson = require("../../package.json");
|
||||||
process.env.APP_VERSION = packageJson.version;
|
process.env.APP_VERSION = packageJson.version;
|
||||||
}
|
}
|
||||||
return process.env.APP_VERSION + "-" + (process.env.GIT_REV?.substring(0, 7) ?? "dev");
|
return process.env.APP_VERSION + "-" + (process.env.GIT_REV?.substring(0, 7) ?? "dev");
|
||||||
|
@ -5,10 +5,10 @@ import { AppService } from "../service/app.service";
|
|||||||
@Controller()
|
@Controller()
|
||||||
export default class AppController {
|
export default class AppController {
|
||||||
@Get("/")
|
@Get("/")
|
||||||
public async index() {
|
public index() {
|
||||||
return {
|
return {
|
||||||
app: "backend",
|
app: "backend",
|
||||||
version: await getAppVersion(),
|
version: getAppVersion(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,9 @@ 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";
|
import ImageController from "./controller/image.controller";
|
||||||
import ReplayController from "./controller/replay.controller";
|
import ReplayController from "./controller/replay.controller";
|
||||||
|
// @ts-ignore
|
||||||
|
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||||
|
import { formatPp } from "@ssr/common/utils/number-utils";
|
||||||
import { ScoreService } from "./service/score.service";
|
import { ScoreService } from "./service/score.service";
|
||||||
|
|
||||||
// Load .env file
|
// Load .env file
|
||||||
@ -95,7 +98,7 @@ app.onError({ as: "global" }, ({ code, error }) => {
|
|||||||
return error.all;
|
return error.all;
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = "status" in error ? error.status : undefined;
|
let status = "status" in error ? error.status : undefined;
|
||||||
return {
|
return {
|
||||||
...((status && { statusCode: status }) || { status: code }),
|
...((status && { statusCode: status }) || { status: code }),
|
||||||
...(error.message != code && { message: error.message }),
|
...(error.message != code && { message: error.message }),
|
||||||
@ -131,10 +134,7 @@ app.use(
|
|||||||
duration: 60 * 1000,
|
duration: 60 * 1000,
|
||||||
max: 100,
|
max: 100,
|
||||||
skip: request => {
|
skip: request => {
|
||||||
// Skip requests to /
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,prefer-const
|
|
||||||
let [_, path] = request.url.split("/"); // Get the url parts
|
let [_, path] = request.url.split("/"); // Get the url parts
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
||||||
path === "" || (path === undefined && (path = "/")); // If we're on /, the path is undefined, so we set it to /
|
path === "" || (path === undefined && (path = "/")); // If we're on /, the path is undefined, so we set it to /
|
||||||
return path === "/"; // ignore all requests to /
|
return path === "/"; // ignore all requests to /
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,8 @@ import NodeCache from "node-cache";
|
|||||||
import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token";
|
import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token";
|
||||||
import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player";
|
import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player";
|
||||||
import { Config } from "../common/config";
|
import { Config } from "../common/config";
|
||||||
import { Jimp } from "jimp";
|
import ky from "ky";
|
||||||
|
import { createCanvas, loadImage } from "canvas";
|
||||||
import { extractColors } from "extract-colors";
|
import { extractColors } from "extract-colors";
|
||||||
|
|
||||||
const cache = new NodeCache({ stdTTL: 60 * 60, checkperiod: 120 });
|
const cache = new NodeCache({ stdTTL: 60 * 60, checkperiod: 120 });
|
||||||
@ -28,27 +29,33 @@ export class ImageService {
|
|||||||
|
|
||||||
return await this.fetchWithCache<{ color: string }>(`average_color-${src}`, async () => {
|
return await this.fetchWithCache<{ color: string }>(`average_color-${src}`, async () => {
|
||||||
try {
|
try {
|
||||||
const image = await Jimp.read(src); // Load image using Jimp
|
const response = await ky.get(src);
|
||||||
const { width, height, data } = image.bitmap; // Access image dimensions and pixel data
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Failed to fetch image: ${src}`);
|
||||||
// Convert the Buffer data to Uint8ClampedArray
|
|
||||||
const uint8ClampedArray = new Uint8ClampedArray(data);
|
|
||||||
|
|
||||||
// Extract the colors using extract-colors
|
|
||||||
const colors = await extractColors({ data: uint8ClampedArray, width, height });
|
|
||||||
|
|
||||||
// Return the most dominant color, or fallback if none found
|
|
||||||
if (colors && colors.length > 0) {
|
|
||||||
return { color: colors[2].hex }; // Returning the third most dominant color
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imageBuffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
// Create an image from the buffer using canvas
|
||||||
|
const img = await loadImage(Buffer.from(imageBuffer));
|
||||||
|
const canvas = createCanvas(img.width, img.height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Draw the image onto the canvas
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
// Get the pixel data from the canvas
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const { data, width, height } = imageData;
|
||||||
|
|
||||||
|
// Extract the colors
|
||||||
|
const color = await extractColors({ data, width, height });
|
||||||
return {
|
return {
|
||||||
color: "#fff", // Fallback color in case no colors are found
|
color: color[2].hex,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching image or extracting colors:", error);
|
|
||||||
return {
|
return {
|
||||||
color: "#fff", // Fallback color in case of an error
|
color: "#fff",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,7 +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";
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||||
import { Config } from "../common/config";
|
import { Config } from "../common/config";
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
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";
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
import { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||||
import { Config } from "../common/config";
|
import { Config } from "../common/config";
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { formatNumberWithCommas, isWholeNumber } from "@ssr/common/utils/number-utils";
|
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DatasetConfig } from "@/components/chart/generic-chart";
|
import { DatasetConfig } from "@/components/chart/generic-chart";
|
||||||
import GenericPlayerChart from "@/components/player/chart/generic-player-chart";
|
import GenericPlayerChart from "@/components/player/chart/generic-player-chart";
|
||||||
import ScoreSaberPlayer from "@ssr/common/types/player/impl/scoresaber-player";
|
import ScoreSaberPlayer from "@ssr/common/types/player/impl/scoresaber-player";
|
||||||
|
import { isWholeNumber } from "@ssr/common/utils/number-utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
player: ScoreSaberPlayer;
|
player: ScoreSaberPlayer;
|
||||||
|
Reference in New Issue
Block a user