Only cache what we need from BeatSaver

This commit is contained in:
Liam 2022-10-22 12:28:46 +01:00
parent efbbd6b6f1
commit 7665cbbae4
5 changed files with 117 additions and 69 deletions

@ -1,55 +0,0 @@
import fetch from "node-fetch";
import sharp from "sharp";
import {
getValue,
setValue,
valueExists,
} from "../../../../src/utils/redisUtils";
const KEY = "BS_MAP_ART_";
/**
*
* @param {Request} req
* @param {Response} res
* @returns
*/
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 valueExists(`${KEY}${mapHash}`.replace(" ", ""));
if (exists) {
const data = await getValue(`${KEY}${mapHash}`);
const buffer = Buffer.from(data, "base64");
res.writeHead(200, {
"Content-Type": "image/" + ext,
"Content-Length": buffer.length,
"Cache-Status": "hit",
});
return res.end(buffer);
}
const before = Date.now();
const data = await fetch(`https://eu.cdn.beatsaver.com/${mapHash}.${ext}`);
if (data.status === 404) {
return res.status(404).json({
status: 404,
message: "Unknown Map Hash",
});
}
let buffer = await data.buffer(); // Change to arrayBuffer at some point to make it shush
buffer = await sharp(buffer).resize(400, 400).toBuffer();
const bytes = buffer.toString("base64");
await setValue(`${KEY}${mapHash}`.replace(" ", ""), bytes);
console.log(
`[Cache]: Cached BS Song Art for hash ${mapHash} in ${
Date.now() - before
}ms`
);
res.setHeader("Cache-Status", "miss");
res.setHeader("Content-Type", "image/" + ext);
res.status(200).send(buffer);
}

@ -11,12 +11,5 @@ export default async function handler(req, res) {
message: "Unknown Map Hash", message: "Unknown Map Hash",
}); });
} }
const data = { res.status(200).json({ status: "OK", data: mapData });
// The maps data from the provided map hash
bsr: mapData.id,
songArt: `http://${req.headers.host}/api/beatsaver/art/${mapHash}?ext=${
mapData.versions[0].coverURL.split("/")[3].split(".")[1]
}`,
};
res.status(200).json({ error: false, data: data });
} }

@ -56,12 +56,11 @@ export default class SongInfo extends Component {
render() { render() {
const data = this.props.data.songData.status.beatmap; const data = this.props.data.songData.status.beatmap;
const beatSaverData = this.props.data.beatSaverData.data; const beatSaverData = this.props.data.beatSaverData.data;
const songArt = beatSaverData.songArt; const mapArt = beatSaverData.mapArt;
const bsr = beatSaverData.bsr; const bsr = beatSaverData.bsr;
const { songName, songAuthorName, difficulty } = data; const { songName, songAuthorName, difficulty } = data;
// what in the fuck is this?? LMFAO
const songTimerPercentage = const songTimerPercentage =
(this.props.data.currentSongTime / 1000 / (data.length / 1000)) * 100000; (this.props.data.currentSongTime / data.length) * 100;
return ( return (
<div className={styles.songInfoContainer}> <div className={styles.songInfoContainer}>
@ -69,7 +68,7 @@ export default class SongInfo extends Component {
width={150} width={150}
height={150} height={150}
alt="Song artwork" alt="Song artwork"
src={songArt} src={mapArt}
loading="lazy" loading="lazy"
placeholder="blur" placeholder="blur"
blurDataURL="https://cdn.fascinated.cc/IkQFyodbZv.jpg?raw=true" blurDataURL="https://cdn.fascinated.cc/IkQFyodbZv.jpg?raw=true"

@ -1,3 +1,4 @@
import { BeatSaverMapData } from "../types/BeatSaverMapData";
import { getValue, setValue, valueExists } from "../utils/redisUtils"; import { getValue, setValue, valueExists } from "../utils/redisUtils";
const BEATSAVER_MAP_API = const BEATSAVER_MAP_API =
@ -6,13 +7,28 @@ const BEATSAVER_MAP_API =
const KEY = "BS_MAP_DATA_"; const KEY = "BS_MAP_DATA_";
function getLatestMapArt(data: BeatSaverMapData) {
console.log(data);
let url: string | undefined = undefined;
for (const version of data.versions) {
url = version.coverURL;
}
console.log(url);
return url;
}
type MapData = {
bsr: string;
mapArt: string | undefined;
};
/** /**
* Gets a specified maps data from BeatSaver * Gets a specified maps data from BeatSaver
* *
* @param {string} hash * @param {string} hash
* @returns The map data * @returns The map data
*/ */
export async function getMapData(hash) { export async function getMapData(hash): Promise<MapData | undefined> {
const mapHash = hash.replace("custom_level_", "").toLowerCase(); const mapHash = hash.replace("custom_level_", "").toLowerCase();
const key = `${KEY}${mapHash}`; const key = `${KEY}${mapHash}`;
@ -31,7 +47,11 @@ export async function getMapData(hash) {
if (data.status === 404) { if (data.status === 404) {
return undefined; return undefined;
} }
const json = await data.json(); const jsonResponse = await data.json();
const json = {
bsr: jsonResponse.id,
mapArt: getLatestMapArt(jsonResponse),
};
await setValue(key, JSON.stringify(json)); await setValue(key, JSON.stringify(json));
console.log( console.log(
`[Cache]: Cached BS Map Data for hash ${mapHash} in ${ `[Cache]: Cached BS Map Data for hash ${mapHash} in ${

@ -0,0 +1,91 @@
export type BeatSaverMapData = {
id: string;
name: string;
description: string;
uploader: {
id: string;
name: string;
hash: string;
avatar: string;
type: string;
admin: boolean;
curator: boolean;
verifiedMapper: boolean;
};
metadata: {
bpm: number;
duration: number;
songName: string;
songSubName: string;
songAuthorName: string;
levelAuthorName: string;
};
stats: {
plays: number;
downloads: number;
upvotes: number;
downvotes: number;
score: number;
};
uploaded: string;
automapper: boolean;
ranked: boolean;
qualified: boolean;
versions: Array<Version>;
curator: Curator;
curatedAt: string;
createdAt: string;
updatedAt: string;
lastPublishedAt: string;
tags: Array<string>;
};
export type Version = {
hash: string;
state: string;
createdAt: string;
sageScore: number;
diffs: Array<Difficulty>;
downloadURL: string;
coverURL: string;
previewURL: string;
};
export type Difficulty = {
njs: number;
offset: number;
notes: number;
bombs: number;
obstacles: number;
nps: number;
length: number;
characteristic: string;
difficulty: DifficultyType;
events: number;
chroma: boolean;
me: boolean;
ne: boolean;
cinema: boolean;
seconds: number;
paritySummary: ParitySummary;
stars: number;
maxScore: number;
};
export type DifficultyType = ["Easy", "Normal", "Hard", "Expert", "ExpertPlus"];
export type ParitySummary = {
errors: number;
warns: number;
resets: number;
};
export type Curator = {
id: number;
name: string;
hash: string;
avatar: string;
type: string;
admin: boolean;
curator: boolean;
};