import { Point } from "@influxdata/influxdb-client"; import axios from "axios"; import { w3cwebsocket as WebsocketClient } from "websocket"; import { InfluxWriteAPI } from ".."; import { connectMongo } from "../db/mongo"; import { LeaderboardSchema } from "../db/schemas/leaderboard"; import { Score } from "../db/schemas/score"; import { Headsets } from "../headsets"; import { normalizedRegionName } from "../utils/regionUtils"; async function update() { const response = await axios.get("https://scoresaber.com/api/players/count"); const count = response.data; const point = new Point("scoresaber") .tag("type", "player_count") .intField("value", parseInt(count)) .timestamp(new Date()); InfluxWriteAPI.writePoint(point); console.log(`Updated player count to ${count}`); } async function connectWebsocket() { await connectMongo(); const leaderboard = await LeaderboardSchema.findOne({ _id: "scoresaber" }); if (!leaderboard) { await LeaderboardSchema.create({ _id: "scoresaber", totalPlays: 0 }); } let totalScores = leaderboard?.totalPlays || 0; let totalCountries = leaderboard?.countries || {}; let totalHeadsets = leaderboard?.headsets || {}; const socket = new WebsocketClient("wss://scoresaber.com/ws"); socket.onopen = () => { console.log("Connected to websocket"); }; socket.onclose = () => { console.log("Disconnected from websocket"); setTimeout(connectWebsocket, 5000); }; socket.onerror = (error) => { console.error("Websocket error:", error); }; socket.onmessage = async (message) => { const data = message.data; let json; try { json = JSON.parse(data.toString()); } catch (e) { return; } const commandName = json.commandName; const commandData = json.commandData; if (commandName == "score") { const { score, leaderboard } = commandData; totalScores++; const { id, hmd, baseScore, missedNotes, badCuts, leaderboardPlayerInfo: player, pp, } = score; const { maxScore, stars, id: leaderboardId } = leaderboard; await Score.deleteOne({ _id: id }); // Delete the score if it already exists const data: any = { _id: id, player: { id: player.id, }, mistakes: { badCuts: badCuts, missed: missedNotes, }, rank: score.rank, maxCombo: score.maxCombo, leaderboardId: leaderboardId, }; if (maxScore) { data.acc = ((baseScore / maxScore) * 100).toFixed(2); } if (pp && stars > 0) { data.pp = pp; data.stars = stars; } Score.create(data); // Create the score const hmdName = Headsets.find((h) => h.id.includes(hmd))?.name || "Unknown"; const countryId = normalizedRegionName(player.country) || "Unknown"; totalCountries[countryId] = (totalCountries[countryId] || 0) + 1; totalHeadsets[hmdName] = (totalHeadsets[hmdName] || 0) + 1; // Update the leaderboard data in the database await LeaderboardSchema.updateOne( { _id: "scoresaber" }, { $set: { totalPlays: totalScores, countries: totalCountries, headsets: totalHeadsets, }, } ).exec(); // Write the metrics data to influx InfluxWriteAPI.writePoint( new Point("scoresaber") .tag("type", "score_count") .intField("value", totalScores) .timestamp(new Date()) ); InfluxWriteAPI.writePoint( new Point("scoresaber") .tag("type", "headsets") .tag("hmd", hmdName) .intField("value", totalHeadsets[hmdName]) .timestamp(new Date()) ); InfluxWriteAPI.writePoint( new Point("scoresaber") .tag("type", "countries") .tag("country", countryId) .intField("value", totalCountries[countryId]) .timestamp(new Date()) ); } }; } async function updateAverages() { const before = new Date().getTime(); Score.find({}).then((scores) => { // create an average for all data for all scores const average = { acc: 0, pp: 0, stars: 0, mistakes: { badCuts: 0, missed: 0, }, rank: 0, maxCombo: 0, }; const rankedScores = scores.filter((score) => score.pp); const scoresWithAccuracy = scores.filter((score) => score.acc); scores.forEach((score) => { if (score.acc) { average.acc += score.acc; } if (score.pp) { average.pp += score.pp; } if (score.stars) { average.stars += score.stars; } if (score.mistakes) { average.mistakes.badCuts += score.mistakes.badCuts; average.mistakes.missed += score.mistakes.missed; } if (score.rank) { average.rank += score.rank; } if (score.maxCombo) { average.maxCombo += score.maxCombo; } }); average.acc /= scoresWithAccuracy.length; average.pp /= rankedScores.length; average.stars /= rankedScores.length; average.mistakes.badCuts /= scores.length; average.mistakes.missed /= scores.length; average.rank /= scores.length; average.maxCombo /= scores.length; // create the point and write it to influx const point = new Point("scoresaber") .tag("type", "average") .timestamp(new Date()); if (average.acc) { point.floatField("acc", average.acc); } if (average.pp) { point.floatField("pp", average.pp); } if (average.stars) { point.floatField("stars", average.stars); } if (average.mistakes) { point.intField( "badCuts", isNaN(average.mistakes.badCuts) ? 0 : average.mistakes.badCuts ); point.intField( "missed", isNaN(average.mistakes.missed) ? 0 : average.mistakes.missed ); } if (average.rank) { point.intField("rank", average.rank); } if (average.maxCombo) { point.intField("maxCombo", average.maxCombo); } InfluxWriteAPI.writePoint(point); console.log(`Updated averages in ${new Date().getTime() - before}ms`); }); } update(); updateAverages(); connectWebsocket(); setInterval(update, 60_000); // 1 minute setInterval(updateAverages, 60_000); // 5 minutes