This commit is contained in:
parent
e0363de4ad
commit
4c6a8c80fb
152
package-lock.json
generated
152
package-lock.json
generated
@ -10,10 +10,12 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@influxdata/influxdb-client": "^1.33.2",
|
||||
"@sentry/node": "^7.77.0",
|
||||
"axios": "^1.6.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^7.6.3",
|
||||
"node-cron": "^3.0.2",
|
||||
"nodemon": "^3.0.1",
|
||||
"typescript": "^5.2.2",
|
||||
"websocket": "^1.0.34"
|
||||
@ -21,6 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/node": "^20.8.9",
|
||||
"@types/node-cron": "^3.0.10",
|
||||
"@types/websocket": "^1.0.8"
|
||||
}
|
||||
},
|
||||
@ -38,6 +41,65 @@
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/tracing": {
|
||||
"version": "7.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz",
|
||||
"integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.77.0",
|
||||
"@sentry/types": "7.77.0",
|
||||
"@sentry/utils": "7.77.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "7.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz",
|
||||
"integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.77.0",
|
||||
"@sentry/utils": "7.77.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz",
|
||||
"integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==",
|
||||
"dependencies": {
|
||||
"@sentry-internal/tracing": "7.77.0",
|
||||
"@sentry/core": "7.77.0",
|
||||
"@sentry/types": "7.77.0",
|
||||
"@sentry/utils": "7.77.0",
|
||||
"https-proxy-agent": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/types": {
|
||||
"version": "7.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz",
|
||||
"integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/utils": {
|
||||
"version": "7.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz",
|
||||
"integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.77.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz",
|
||||
@ -101,6 +163,12 @@
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-cron": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.10.tgz",
|
||||
"integrity": "sha512-8thdLSpV7na8+bsmiyMH/KKQWOBg4WmoLInlGxWz7JJn8Mie+53QWaygSlc7f/AsMsCB5bBTVjoueAU6tRutTw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz",
|
||||
@ -174,6 +242,38 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@ -795,6 +895,39 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@ -1087,6 +1220,17 @@
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
|
||||
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
|
||||
"dependencies": {
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
|
||||
@ -1568,6 +1712,14 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
@ -12,10 +12,12 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@influxdata/influxdb-client": "^1.33.2",
|
||||
"@sentry/node": "^7.77.0",
|
||||
"axios": "^1.6.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^7.6.3",
|
||||
"node-cron": "^3.0.2",
|
||||
"nodemon": "^3.0.1",
|
||||
"typescript": "^5.2.2",
|
||||
"websocket": "^1.0.34"
|
||||
@ -23,6 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/node": "^20.8.9",
|
||||
"@types/node-cron": "^3.0.10",
|
||||
"@types/websocket": "^1.0.8"
|
||||
}
|
||||
}
|
||||
|
47
src/api/api.ts
Normal file
47
src/api/api.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
import express from "express";
|
||||
import analyticsRoute from "./routes/analytics";
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://19a8f6e661f41ed26ec9c221954594cf@sentry.fascinated.cc/3",
|
||||
integrations: [
|
||||
// enable HTTP calls tracing
|
||||
new Sentry.Integrations.Http({ tracing: true }),
|
||||
// enable Express.js middleware tracing
|
||||
new Sentry.Integrations.Express({ app }),
|
||||
new Sentry.Integrations.OnUncaughtException(),
|
||||
new Sentry.Integrations.OnUnhandledRejection(),
|
||||
new Sentry.Integrations.Mongo({
|
||||
useMongoose: true,
|
||||
}),
|
||||
],
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
||||
// Routes
|
||||
analyticsRoute(app);
|
||||
|
||||
// The request handler must be the first middleware on the app
|
||||
app.use(Sentry.Handlers.requestHandler());
|
||||
|
||||
// TracingHandler creates a trace for every incoming request
|
||||
app.use(Sentry.Handlers.tracingHandler());
|
||||
|
||||
// The error handler must be registered before any other error middleware and after all controllers
|
||||
app.use(Sentry.Handlers.errorHandler());
|
||||
|
||||
// Optional fallthrough error handler
|
||||
app.use(function onError(err: any, req: any, res: any, next: any) {
|
||||
// The error id is attached to `res.sentry` to be returned
|
||||
// and optionally displayed to the user for support.
|
||||
res.statusCode = 500;
|
||||
res.end(res.sentry + "\n");
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`API Server is running on http://localhost:${port}`);
|
||||
});
|
85
src/api/routes/analytics.ts
Normal file
85
src/api/routes/analytics.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { INFLUXDB_BUCKET, InfluxQueryAPI } from "../..";
|
||||
import { formatString } from "../../utils/stringUtils";
|
||||
import { parseTimeToMilliseconds } from "../../utils/timeUtils";
|
||||
|
||||
// Query to get the player count history for tge last 24 hours in 1 hour intervals
|
||||
const getPlayerHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||
|> range(start: -{})
|
||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r["type"] == "player_count")
|
||||
|> aggregateWindow(every: {}, fn: mean)
|
||||
|> yield()
|
||||
`;
|
||||
|
||||
const getScoreCountHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||
|> range(start: -{})
|
||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r["type"] == "score_count")
|
||||
|> aggregateWindow(every: {}, fn: spread, createEmpty: true)
|
||||
`;
|
||||
|
||||
export default function analyticsRoute(app: any) {
|
||||
app.get("/analytics", async (req: any, res: any) => {
|
||||
const before = new Date().getTime();
|
||||
|
||||
const timeQuery = req.query.time || "30d";
|
||||
const timeInMs = parseTimeToMilliseconds(timeQuery.toString());
|
||||
if (timeInMs > 30 * 24 * 60 * 60 * 1000) {
|
||||
return res.status(400).json({
|
||||
error: "Time range too large. Max time range is 30 days.",
|
||||
});
|
||||
}
|
||||
const shouldUseLongerIntervals = timeInMs > 24 * 60 * 60 * 1000 * 7; // 7 days
|
||||
|
||||
const getActivePlayersHistory = async () => {
|
||||
const rows = await InfluxQueryAPI.collectRows(
|
||||
formatString(
|
||||
getPlayerHistoryQuery,
|
||||
false,
|
||||
timeQuery,
|
||||
shouldUseLongerIntervals ? "1d" : "1h"
|
||||
)
|
||||
);
|
||||
let history = rows.map((row: any) => ({
|
||||
time: row._time,
|
||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||
}));
|
||||
return history.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||
);
|
||||
};
|
||||
|
||||
const getScoreCountHistory = async () => {
|
||||
const rows = await InfluxQueryAPI.collectRows(
|
||||
formatString(
|
||||
getScoreCountHistoryQuery,
|
||||
false,
|
||||
timeQuery,
|
||||
shouldUseLongerIntervals ? "1d" : "1h"
|
||||
)
|
||||
);
|
||||
let history = rows.map((row: any) => ({
|
||||
time: row._time,
|
||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||
}));
|
||||
return history.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||
);
|
||||
};
|
||||
|
||||
const [activePlayersHistory, scoreCountHistory] = await Promise.all([
|
||||
getActivePlayersHistory(),
|
||||
getScoreCountHistory(),
|
||||
]);
|
||||
|
||||
return res.json({
|
||||
serverTimeTaken: new Date().getTime() - before + "ms",
|
||||
activePlayersHistory,
|
||||
scoreCountHistory,
|
||||
});
|
||||
});
|
||||
}
|
@ -23,4 +23,4 @@ export const InfluxWriteAPI = influxClient.getWriteApi(
|
||||
export const InfluxQueryAPI = influxClient.getQueryApi(INFLUXDB_ORG);
|
||||
|
||||
require("./services/updateData");
|
||||
require("./services/api");
|
||||
require("./api/api");
|
||||
|
@ -1,95 +0,0 @@
|
||||
import express from "express";
|
||||
import { INFLUXDB_BUCKET, InfluxQueryAPI } from "../index";
|
||||
import { formatString } from "../utils/stringUtils";
|
||||
import { parseTimeToMilliseconds } from "../utils/timeUtils";
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Query to get the player count history for tge last 24 hours in 1 hour intervals
|
||||
const getPlayerHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||
|> range(start: -{})
|
||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r["type"] == "player_count")
|
||||
|> aggregateWindow(every: {}, fn: mean)
|
||||
|> yield()
|
||||
`;
|
||||
|
||||
const getScoreCountHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||
|> range(start: -{})
|
||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r["type"] == "score_count")
|
||||
|> aggregateWindow(every: {}, fn: spread, createEmpty: true)
|
||||
`;
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Hello!");
|
||||
});
|
||||
|
||||
app.get("/analytics", async (req, res) => {
|
||||
const before = new Date().getTime();
|
||||
|
||||
const timeQuery = req.query.time || "30d";
|
||||
const timeInMs = parseTimeToMilliseconds(timeQuery.toString());
|
||||
if (timeInMs > 30 * 24 * 60 * 60 * 1000) {
|
||||
return res.status(400).json({
|
||||
error: "Time range too large. Max time range is 30 days.",
|
||||
});
|
||||
}
|
||||
const shouldUseLongerIntervals = timeInMs > 24 * 60 * 60 * 1000 * 7; // 7 days
|
||||
|
||||
const getActivePlayersHistory = async () => {
|
||||
const rows = await InfluxQueryAPI.collectRows(
|
||||
formatString(
|
||||
getPlayerHistoryQuery,
|
||||
false,
|
||||
timeQuery,
|
||||
shouldUseLongerIntervals ? "1d" : "1h"
|
||||
)
|
||||
);
|
||||
let history = rows.map((row: any) => ({
|
||||
time: row._time,
|
||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||
}));
|
||||
return history.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||
);
|
||||
};
|
||||
|
||||
const getScoreCountHistory = async () => {
|
||||
const rows = await InfluxQueryAPI.collectRows(
|
||||
formatString(
|
||||
getScoreCountHistoryQuery,
|
||||
false,
|
||||
timeQuery,
|
||||
shouldUseLongerIntervals ? "1d" : "1h"
|
||||
)
|
||||
);
|
||||
let history = rows.map((row: any) => ({
|
||||
time: row._time,
|
||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||
}));
|
||||
return history.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||
);
|
||||
};
|
||||
|
||||
const [activePlayersHistory, scoreCountHistory] = await Promise.all([
|
||||
getActivePlayersHistory(),
|
||||
getScoreCountHistory(),
|
||||
]);
|
||||
|
||||
return res.json({
|
||||
serverTimeTaken: new Date().getTime() - before + "ms",
|
||||
activePlayersHistory,
|
||||
scoreCountHistory,
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`API Server is running on http://localhost:${port}`);
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import { Point } from "@influxdata/influxdb-client";
|
||||
import axios from "axios";
|
||||
import cron from "node-cron";
|
||||
import { w3cwebsocket as WebsocketClient } from "websocket";
|
||||
import { InfluxWriteAPI } from "..";
|
||||
import { connectMongo } from "../db/mongo";
|
||||
@ -18,6 +19,7 @@ async function update() {
|
||||
.intField("value", parseInt(count))
|
||||
.timestamp(new Date());
|
||||
InfluxWriteAPI.writePoint(point);
|
||||
console.log(`Updated player count to ${count}`);
|
||||
|
||||
if (totalScores) {
|
||||
InfluxWriteAPI.writePoint(
|
||||
@ -26,9 +28,8 @@ async function update() {
|
||||
.intField("value", totalScores)
|
||||
.timestamp(new Date())
|
||||
);
|
||||
console.log(`Updated score count to ${totalScores}`);
|
||||
}
|
||||
|
||||
console.log(`Updated player count to ${count}`);
|
||||
}
|
||||
|
||||
async function connectWebsocket() {
|
||||
@ -83,7 +84,7 @@ async function connectWebsocket() {
|
||||
leaderboardPlayerInfo: player,
|
||||
pp,
|
||||
} = score;
|
||||
const { maxScore, stars, id: leaderboardId } = leaderboard;
|
||||
const { maxScore, stars } = leaderboard;
|
||||
|
||||
const hmdName = Headsets[hmd] || "Unknown";
|
||||
const countryId = normalizedRegionName(player.country) || "Unknown";
|
||||
@ -144,4 +145,4 @@ async function connectWebsocket() {
|
||||
|
||||
update();
|
||||
connectWebsocket();
|
||||
setInterval(update, 60_000); // 1 minute
|
||||
cron.schedule("*/5 * * * *", update);
|
||||
|
Loading…
Reference in New Issue
Block a user