diff --git a/pages/api/beatleader/stars.js b/pages/api/beatleader/stars.js index 54c7b58..5f5a74e 100644 --- a/pages/api/beatleader/stars.js +++ b/pages/api/beatleader/stars.js @@ -1,6 +1,6 @@ import fetch from "node-fetch"; import WebsiteTypes from "../../../src/consts/LeaderboardType"; -import RedisUtils from "../../../src/utils/redisUtils"; +import { getValue, setValue, valueExists } from "../../../src/utils/redisUtils"; const KEY = "BL_MAP_STAR_"; @@ -16,9 +16,9 @@ export default async function handler(req, res) { const characteristic = req.query.characteristic; const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`; - const exists = await RedisUtils.exists(key); + const exists = await valueExists(key); if (exists) { - const data = await RedisUtils.getValue(key); + const data = await getValue(key); res.setHeader("Cache-Status", "hit"); return res.status(200).json({ @@ -59,7 +59,7 @@ export default async function handler(req, res) { message: "Unknown Map Hash", }); } - await RedisUtils.setValue(key, starCount); + await setValue(key, starCount); console.log( `[Cache]: Cached BL Star Count for hash ${mapHash} in ${ Date.now() - before diff --git a/pages/api/beatsaver/art/[hash].js b/pages/api/beatsaver/art/[hash].js index 8f5d47e..c2512ac 100644 --- a/pages/api/beatsaver/art/[hash].js +++ b/pages/api/beatsaver/art/[hash].js @@ -1,6 +1,10 @@ import fetch from "node-fetch"; import sharp from "sharp"; -import RedisUtils from "../../../../src/utils/redisUtils"; +import { + getValue, + setValue, + valueExists, +} from "../../../../src/utils/redisUtils"; const KEY = "BS_MAP_ART_"; @@ -14,9 +18,9 @@ export default async function handler(req, res) { const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase(); const ext = req.query.ext || "jpg"; - const exists = await RedisUtils.exists(`${KEY}${mapHash}`.replace(" ", "")); + const exists = await valueExists(`${KEY}${mapHash}`.replace(" ", "")); if (exists) { - const data = await RedisUtils.getValue(`${KEY}${mapHash}`); + const data = await getValue(`${KEY}${mapHash}`); const buffer = Buffer.from(data, "base64"); res.writeHead(200, { "Content-Type": "image/" + ext, @@ -39,7 +43,7 @@ export default async function handler(req, res) { buffer = await sharp(buffer).resize(400, 400).toBuffer(); const bytes = buffer.toString("base64"); - await RedisUtils.setValue(`${KEY}${mapHash}`.replace(" ", ""), bytes); + await setValue(`${KEY}${mapHash}`.replace(" ", ""), bytes); console.log( `[Cache]: Cached BS Song Art for hash ${mapHash} in ${ Date.now() - before diff --git a/pages/api/beatsaver/map.js b/pages/api/beatsaver/map.js index 2fc7216..04185c4 100644 --- a/pages/api/beatsaver/map.js +++ b/pages/api/beatsaver/map.js @@ -1,9 +1,9 @@ -import Utils from "../../../utils/utils"; +import { getMapData } from "../../../src/helpers/beatSaverHelpers"; export default async function handler(req, res) { const mapHash = req.query.hash; - const mapData = await Utils.getMapData(mapHash.replace("custom_level_", "")); + const mapData = await getMapData(mapHash); if (mapData === undefined) { // Check if a map hash was provided return res.status(404).json({ diff --git a/pages/api/scoresaber/stars.js b/pages/api/scoresaber/stars.js index b616ac3..884a21f 100644 --- a/pages/api/scoresaber/stars.js +++ b/pages/api/scoresaber/stars.js @@ -1,6 +1,6 @@ import fetch from "node-fetch"; import WebsiteTypes from "../../../src/consts/LeaderboardType"; -import RedisUtils from "../../../src/utils/redisUtils"; +import { getValue, setValue, valueExists } from "../../../src/utils/redisUtils"; import { diffToScoreSaberDiff } from "../../../src/utils/scoreSaberUtils"; const KEY = "SS_MAP_STAR_"; @@ -17,9 +17,9 @@ export default async function handler(req, res) { const characteristic = req.query.characteristic; const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`; - const exists = await RedisUtils.exists(key); + const exists = await valueExists(key); if (exists) { - const data = await RedisUtils.getValue(key); + const data = await getValue(key); res.setHeader("Cache-Status", "hit"); return res.status(200).json({ @@ -55,7 +55,7 @@ export default async function handler(req, res) { message: "Unknown Map Hash", }); } - await RedisUtils.setValue(key, starCount); + await setValue(key, starCount); console.log( `[Cache]: Cached SS Star Count for hash ${mapHash} in ${ Date.now() - before diff --git a/pages/api/steamavatar.js b/pages/api/steamavatar.js index 303f6bb..9c787de 100644 --- a/pages/api/steamavatar.js +++ b/pages/api/steamavatar.js @@ -1,7 +1,7 @@ import fetch from "node-fetch"; import sharp from "sharp"; import { isValidSteamId } from "../../src/helpers/validateSteamId"; -import RedisUtils from "../../src/utils/redisUtils"; +import { getValue, setValue, valueExists } from "../../src/utils/redisUtils"; const KEY = "STEAM_AVATAR_"; const ext = "jpg"; @@ -22,9 +22,9 @@ export default async function handler(req, res) { }); } - const exists = await RedisUtils.exists(`${KEY}${steamId}`); + const exists = await valueExists(`${KEY}${steamId}`); if (exists) { - const data = await RedisUtils.getValue(`${KEY}${steamId}`); + const data = await getValue(`${KEY}${steamId}`); const buffer = Buffer.from(data, "base64"); res.writeHead(200, { "Content-Type": "image/" + ext, @@ -49,7 +49,7 @@ export default async function handler(req, res) { buffer = await sharp(buffer).resize(400, 400).toBuffer(); const bytes = buffer.toString("base64"); - await RedisUtils.setValue(`${KEY}${steamId}`, bytes); + await setValue(`${KEY}${steamId}`, bytes); console.log( `[Cache]: Cached Avatar for id ${steamId} in ${Date.now() - before}ms` ); diff --git a/src/curve/BeatLeaderCurve.js b/src/curve/BeatLeaderCurve.js index 2c6fc1b..b6177a9 100644 --- a/src/curve/BeatLeaderCurve.js +++ b/src/curve/BeatLeaderCurve.js @@ -1,18 +1,15 @@ -export default class BeatLeaderCurve { - static curve(acc, stars) { - var l = 1 - (0.03 * (stars - 3.0)) / 11.0; - var a = 0.96 * l; - var f = 1.2 - (0.6 * stars) / 14.0; +function curve(acc, stars) { + var l = 1 - (0.03 * (stars - 3.0)) / 11.0; + var a = 0.96 * l; + var f = 1.2 - (0.6 * stars) / 14.0; - return Math.pow(Math.log10(l / (l - acc)) / Math.log10(l / (l - a)), f); - } - - static getPP(acc, stars) { - if (stars === undefined || acc === undefined) { - return undefined; - } - const pp = - BeatLeaderCurve.curve(acc / 100, stars - 0.5) * (stars + 0.5) * 42; - return Number.isNaN(pp) ? undefined : pp; - } + return Math.pow(Math.log10(l / (l - acc)) / Math.log10(l / (l - a)), f); +} + +export function getBeatLeaderPP(acc, stars) { + if (stars === undefined || acc === undefined) { + return undefined; + } + const pp = curve(acc / 100, stars - 0.5) * (stars + 0.5) * 42; + return Number.isNaN(pp) ? undefined : pp; } diff --git a/src/curve/ScoreSaberCurve.js b/src/curve/ScoreSaberCurve.js index 207ac0a..e282ff9 100644 --- a/src/curve/ScoreSaberCurve.js +++ b/src/curve/ScoreSaberCurve.js @@ -1,83 +1,79 @@ // Yoinked from https://github.com/Shurdoof/pp-calculator/blob/c24b5ca452119339928831d74e6d603fb17fd5ef/src/lib/pp/calculator.ts // Thank for for this I have no fucking idea what the maths is doing but it works! -export default class ScoreSaberCurve { - static starMultiplier = 42.11; - static ppCurve = [ - [1, 7], - [0.999, 6.24], - [0.9975, 5.31], - [0.995, 4.14], - [0.9925, 3.31], - [0.99, 2.73], - [0.9875, 2.31], - [0.985, 2.0], - [0.9825, 1.775], - [0.98, 1.625], - [0.9775, 1.515], - [0.975, 1.43], - [0.9725, 1.36], - [0.97, 1.3], - [0.965, 1.195], - [0.96, 1.115], - [0.955, 1.05], - [0.95, 1], - [0.94, 0.94], - [0.93, 0.885], - [0.92, 0.835], - [0.91, 0.79], - [0.9, 0.75], - [0.875, 0.655], - [0.85, 0.57], - [0.825, 0.51], - [0.8, 0.47], - [0.75, 0.4], - [0.7, 0.34], - [0.65, 0.29], - [0.6, 0.25], - [0.0, 0.0], - ]; - static clamp(value, min, max) { - if (min !== null && value < min) { - return min; +const starMultiplier = 42.11; +const ppCurve = [ + [1, 7], + [0.999, 6.24], + [0.9975, 5.31], + [0.995, 4.14], + [0.9925, 3.31], + [0.99, 2.73], + [0.9875, 2.31], + [0.985, 2.0], + [0.9825, 1.775], + [0.98, 1.625], + [0.9775, 1.515], + [0.975, 1.43], + [0.9725, 1.36], + [0.97, 1.3], + [0.965, 1.195], + [0.96, 1.115], + [0.955, 1.05], + [0.95, 1], + [0.94, 0.94], + [0.93, 0.885], + [0.92, 0.835], + [0.91, 0.79], + [0.9, 0.75], + [0.875, 0.655], + [0.85, 0.57], + [0.825, 0.51], + [0.8, 0.47], + [0.75, 0.4], + [0.7, 0.34], + [0.65, 0.29], + [0.6, 0.25], + [0.0, 0.0], +]; + +function clamp(value, min, max) { + if (min !== null && value < min) { + return min; + } + + if (max !== null && value > max) { + return max; + } + + return value; +} + +function lerp(v0, v1, t) { + return v0 + t * (v1 - v0); +} + +function calculatePPModifier(c1, c2, acc) { + const distance = (c2[0] - acc) / (c2[0] - c1[0]); + return lerp(c2[1], c1[1], distance); +} + +function findPPModifier(acc, curve) { + acc = clamp(acc, 0, 100) / 100; + + let prev = curve[1]; + for (const item of curve) { + if (item[0] <= acc) { + return calculatePPModifier(item, prev, acc); } - - if (max !== null && value > max) { - return max; - } - - return value; - } - - static lerp(v0, v1, t) { - return v0 + t * (v1 - v0); - } - - static calculatePPModifier(c1, c2, acc) { - const distance = (c2[0] - acc) / (c2[0] - c1[0]); - return ScoreSaberCurve.lerp(c2[1], c1[1], distance); - } - - static findPPModifier(acc, curve) { - acc = ScoreSaberCurve.clamp(acc, 0, 100) / 100; - - let prev = curve[1]; - for (const item of curve) { - if (item[0] <= acc) { - return ScoreSaberCurve.calculatePPModifier(item, prev, acc); - } - prev = item; - } - } - - static getPP(acc, stars) { - const ppValue = stars * ScoreSaberCurve.starMultiplier; - const modifier = ScoreSaberCurve.findPPModifier( - acc, - ScoreSaberCurve.ppCurve - ); - - const finalPP = modifier * ppValue; - return Number.isNaN(finalPP) ? undefined : finalPP; + prev = item; } } + +export function getScoreSaberPP(acc, stars) { + const ppValue = stars * starMultiplier; + const modifier = findPPModifier(acc, ppCurve); + + const finalPP = modifier * ppValue; + return Number.isNaN(finalPP) ? undefined : finalPP; +} diff --git a/src/helpers/beatSaverHelpers.js b/src/helpers/beatSaverHelpers.js new file mode 100644 index 0000000..2a83d26 --- /dev/null +++ b/src/helpers/beatSaverHelpers.js @@ -0,0 +1,42 @@ +import { getValue, setValue, valueExists } from "../utils/redisUtils"; + +const BEATSAVER_MAP_API = + process.env.NEXT_PUBLIC_HTTP_PROXY + + "/https://api.beatsaver.com/maps/hash/%s"; + +const KEY = "BS_MAP_DATA_"; + +/** + * Gets a specified maps data from BeatSaver + * + * @param {string} hash + * @returns The map data + */ +export async function getMapData(hash) { + const mapHash = hash.replace("custom_level_", "").toLowerCase(); + + const key = `${KEY}${mapHash}`; + const exists = await valueExists(key); + if (exists) { + const data = await getValue(key); + return JSON.parse(data); + } + + const before = Date.now(); + const data = await fetch(BEATSAVER_MAP_API.replace("%s", mapHash), { + headers: { + "X-Requested-With": "BeatSaber Overlay", + }, + }); + if (data.status === 404) { + return undefined; + } + const json = await data.json(); + await setValue(key, JSON.stringify(json)); + console.log( + `[Cache]: Cached BS Map Data for hash ${mapHash} in ${ + Date.now() - before + }ms` + ); + return json; +} diff --git a/src/helpers/validateSteamId.js b/src/helpers/validateSteamId.js index 69464c7..eba5d7e 100644 --- a/src/helpers/validateSteamId.js +++ b/src/helpers/validateSteamId.js @@ -1,5 +1,5 @@ import { default as LeaderboardType } from "../consts/LeaderboardType"; -import RedisUtils from "../utils/redisUtils"; +import { getValue, setValue, valueExists } from "../utils/redisUtils"; import Utils from "../utils/utils"; const KEY = "VALID_STEAM_ID_"; @@ -8,7 +8,7 @@ const TO_CHECK = [ LeaderboardType.BeatLeader.ApiUrl.PlayerData, ]; -async function isValidSteamId(steamId) { +export async function isValidSteamId(steamId) { if (!steamId) { return false; } @@ -16,9 +16,9 @@ async function isValidSteamId(steamId) { return false; } - const exists = await RedisUtils.exists(`${KEY}${steamId}`); + const exists = await valueExists(`${KEY}${steamId}`); if (exists) { - const data = await RedisUtils.getValue(`${KEY}${steamId}`); + const data = await getValue(`${KEY}${steamId}`); return Boolean(data); } @@ -32,13 +32,9 @@ async function isValidSteamId(steamId) { } } - await RedisUtils.setValue(`${KEY}${steamId}`, valid); + await setValue(`${KEY}${steamId}`, valid); console.log( `[Cache]: Cached Steam ID for id ${steamId} in ${Date.now() - before}ms` ); return valid; } - -module.exports = { - isValidSteamId, -}; diff --git a/src/utils/redisUtils.js b/src/utils/redisUtils.js index c8747f0..ba99a43 100644 --- a/src/utils/redisUtils.js +++ b/src/utils/redisUtils.js @@ -8,7 +8,7 @@ const client = new Redis({ }); client.connect().catch(() => {}); -async function setValue(key, value, expireAt = 86400) { +export async function setValue(key, value, expireAt = 86400) { if (client.status === "close" || client.status === "end") { await client.connect().catch(() => {}); } @@ -16,7 +16,7 @@ async function setValue(key, value, expireAt = 86400) { await client.set(key, value, "EX", expireAt); } -async function getValue(key) { +export async function getValue(key) { if (client.status === "close" || client.status === "end") { await client.connect().catch(() => {}); } @@ -34,7 +34,7 @@ async function getValue(key) { return maybe; } -async function exists(key) { +export async function valueExists(key) { if (client.status === "close" || client.status === "end") { await client.connect().catch(() => {}); } @@ -51,9 +51,3 @@ async function exists(key) { const maybe = await yes; return maybe == 1 ? true : false; } - -module.exports = { - getValue, - setValue, - exists, -}; diff --git a/src/utils/scoreSaberUtils.js b/src/utils/scoreSaberUtils.js index 9e16849..1be68b6 100644 --- a/src/utils/scoreSaberUtils.js +++ b/src/utils/scoreSaberUtils.js @@ -1,8 +1,4 @@ -function diffToScoreSaberDiff(diff) { - console.log( - "🚀 ~ file: scoreSaberUtils.js ~ line 2 ~ diffToScoreSaberDiff ~ diff", - diff - ); +export function diffToScoreSaberDiff(diff) { switch (diff) { case "Easy": { return 1; @@ -24,7 +20,3 @@ function diffToScoreSaberDiff(diff) { } } } - -module.exports = { - diffToScoreSaberDiff, -}; diff --git a/src/utils/utils.js b/src/utils/utils.js index 5928b21..f9ee1f8 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,6 +1,6 @@ import { default as LeaderboardType } from "../consts/LeaderboardType"; -import BeatLeaderCurve from "../curve/BeatLeaderCurve"; -import ScoreSaberCurve from "../curve/ScoreSaberCurve"; +import { getBeatLeaderPP } from "../curve/BeatLeaderCurve"; +import { getScoreSaberPP } from "../curve/ScoreSaberCurve"; export default class Utils { /** @@ -32,10 +32,10 @@ export default class Utils { static calculatePP(stars, acc, type) { if (type === "BeatLeader") { - return BeatLeaderCurve.getPP(acc, stars); + return getBeatLeaderPP(acc, stars); } if (type === "ScoreSaber") { - return ScoreSaberCurve.getPP(acc, stars); + return getScoreSaberPP(acc, stars); } return undefined; } diff --git a/utils/utils.js b/utils/utils.js deleted file mode 100644 index 4dc3368..0000000 --- a/utils/utils.js +++ /dev/null @@ -1,36 +0,0 @@ -const mapCache = new Map(); - -module.exports = { - BEATSAVER_MAP_API: - process.env.NEXT_PUBLIC_HTTP_PROXY + - "/https://api.beatsaver.com/maps/hash/%s", - - /** - * Gets a specified maps data from BeatSaver - * - * @param {string} hash - * @returns The map data - */ - async getMapData(hash) { - hash = this.BEATSAVER_MAP_API.replace("%s", hash); - if (mapCache.has(hash)) { - // Return from cache - return mapCache.get(hash); - } - - const data = await fetch(hash, { - headers: { - origin: "Fascinated Overlay", - }, - }); - if (data.status === 404) { - return undefined; - } - const json = await data.json(); - mapCache.set(hash, json); - setTimeout(() => { - mapCache.delete(hash); - }, 60 * 60 * 1000); // 1h - return json; - }, -};