code cleanup for v3

This commit is contained in:
Aleksander Wilczyński 2024-12-13 20:33:20 +01:00
parent 9c84989063
commit e2732c2008
Signed by untrusted user: alekswilc
GPG Key ID: D4464A248E5F27FE
11 changed files with 0 additions and 682 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,85 +0,0 @@
import { Router } from 'express';
import dayjs from 'dayjs';
import { msToTime } from '../../util/time.js';
import { PipelineStage } from 'mongoose';
import { MProfile, raw_schema } from '../../mongo/profile.js';
import { GitUtil } from '../../util/git.js';
import { escapeRegexString } from '../../util/functions.js';
const generateSearch = (regex: RegExp) => [
{
steam: { $regex: regex },
},
{
steamName: { $regex: regex },
},
]
export class LeaderboardRoute {
static load() {
const app = Router();
app.get('/train', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(escapeRegexString(x), "i"));
const filter: PipelineStage[] = [];
s && filter.push({
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
})
const records = await MProfile.aggregate(filter)
.sort({ trainPoints: -1 })
.limit(10)
res.render('leaderboard/index.ejs', {
records,
dayjs,
msToTime,
type: 'train',
q: req.query.q,
...GitUtil.getData(),
});
})
app.get('/station', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(escapeRegexString(x), "i"));
const filter: PipelineStage[] = [];
s && filter.push({
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
})
const records = await MProfile.aggregate(filter)
.sort({ dispatcherTime: -1 })
.limit(10)
res.render('leaderboard/index.ejs', {
records,
dayjs,
msToTime,
type: 'station',
q: req.query.q,
...GitUtil.getData(),
});
})
return app;
}
}

View File

@ -1,98 +0,0 @@
import { Router } from 'express';
import { MLog } from '../../mongo/logs.js';
import dayjs from 'dayjs';
import { msToTime } from '../../util/time.js';
import { PipelineStage } from 'mongoose';
import { MBlacklist } from '../../mongo/blacklist.js';
import { SteamUtil } from '../../util/SteamUtil.js';
import { GitUtil } from '../../util/git.js';
import { escapeRegexString } from '../../util/functions.js';
const generateSearch = (regex: RegExp) => [
{
stationName: { $regex: regex },
},
{
userUsername: { $regex: regex },
},
{
stationShort: { $regex: regex },
},
{
userSteamId: { $regex: regex },
},
{
server: { $regex: regex },
}
]
export class StationsRoute {
static load() {
const app = Router();
app.get('/', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(escapeRegexString(x), "i"));
const filter: PipelineStage[] = [];
s && filter.push({
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
})
const records = await MLog.aggregate(filter)
.sort({ leftDate: -1 })
.limit(30)
res.render('stations/index.ejs', {
records,
dayjs,
q: req.query.q,
msToTime,
...GitUtil.getData()
});
})
app.get('/details/:id', async (req, res) => {
if (!req.params.id) return res.redirect('/stations/');
const record = await MLog.findOne({ id: req.params.id });
const blacklist = await MBlacklist.findOne({ steam: record?.userSteamId! });
if (blacklist && blacklist.status) return res.redirect('/stations/');
const player = await SteamUtil.getPlayer(record?.userSteamId!);
res.render('stations/details.ejs', {
record,
dayjs,
player,
msToTime,
...GitUtil.getData()
});
})
// API ENDPOINTS
// CREATE AN ISSUE IF YOU NEED API ACCESS: https://git.alekswilc.dev/alekswilc/simrail-logs/issues
/*
app.get('/api/last', async (req, res) => {
const records = await MLog.find()
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})
app.get('/api/search', async (req, res) => {
if (!req.query.q) return res.send('invalid');
const records = await MLog.find({ $text: { $search: req.query.q as string } })
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})*/
return app;
}
}

View File

@ -1,133 +0,0 @@
import { Router } from 'express';
import dayjs from 'dayjs';
import { msToTime } from '../../util/time.js';
import { PipelineStage } from 'mongoose';
import { MTrainLog, raw_schema } from '../../mongo/trainLogs.js';
import { MBlacklist } from '../../mongo/blacklist.js';
import { SteamUtil } from '../../util/SteamUtil.js';
import { GitUtil } from '../../util/git.js';
import { escapeRegexString } from '../../util/functions.js';
const generateSearch = (regex: RegExp) => [
{
trainNumber: { $regex: regex },
},
{
userSteamId: { $regex: regex },
},
{
server: { $regex: regex },
},
{
userUsername: { $regex: regex },
},
]
export class TrainsRoute {
static load() {
const app = Router();
app.get('/', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(escapeRegexString(x), "i"));
const filter: PipelineStage[] = [];
s && filter.push({
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
})
const records = await MTrainLog.aggregate(filter)
.sort({ leftDate: -1 })
.limit(30)
res.render('trains/index.ejs', {
records,
dayjs,
q: req.query.q,
msToTime,
...GitUtil.getData()
});
})
app.get('/details/:id', async (req, res) => {
if (!req.params.id) return res.redirect('/trains/');
const record = await MTrainLog.findOne({ id: req.params.id });
const player = await SteamUtil.getPlayer(record?.userSteamId!);
const blacklist = await MBlacklist.findOne({ steam: record?.userSteamId! });
if (blacklist && blacklist.status) return res.redirect('/trains/');
res.render('trains/details.ejs', {
record,
dayjs,
player,
msToTime,
...GitUtil.getData()
});
})
app.get('/leaderboard/', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(escapeRegexString(x), "i"));
const data = Object.keys(raw_schema)
.reduce((o, key) => ({ ...o, [key]: `$${key}` }), {});
const filter: PipelineStage[] = [
{
$project: {
// record.leftDate - record.joinedDate
result: { $subtract: ['$leftDate', '$joinedDate'] },
...data
}
},
];
s && filter.unshift(
{
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
}
)
const records = await MTrainLog.aggregate(filter)
.sort({ result: -1 })
.limit(30)
res.render('trains/leaderboard.ejs', {
records,
dayjs,
q: req.query.q,
msToTime
});
})
// API ENDPOINTS
// CREATE AN ISSUE IF YOU NEED API ACCESS: https://git.alekswilc.dev/alekswilc/simrail-logs/issues
/*
app.get('/api/last', async (req, res) => {
const records = await MLog.find()
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})
app.get('/api/search', async (req, res) => {
if (!req.query.q) return res.send('invalid');
const records = await MLog.find({ $text: { $search: req.query.q as string } })
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})*/
return app;
}
}

View File

@ -1,51 +0,0 @@
import { Server, Station } from '@simrail/types';
import { MLog } from '../mongo/logs.js';
import { IPlayer } from '../types/player.js';
import { SimrailClientEvents } from '../util/SimrailClient.js';
import { v4 } from 'uuid';
import { MProfile } from '../mongo/profile.js';
import { SteamUtil } from '../util/SteamUtil.js';
export class StationsModule {
public static load() {
client.on(SimrailClientEvents.StationLeft, async (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
const stats = await SteamUtil.getPlayerStats(player.steamid);
const date = new Date();
if (stats) {
const time = joinedAt ? (date.getTime() - joinedAt) : 0;
const userProfile = await MProfile.findOne({ steam: player.steamid }) ?? await MProfile.create({ steam: player.steamid, id: v4(), steamName: player.personaname });
if (!userProfile.dispatcherStats) userProfile.dispatcherStats = {};
if (userProfile.dispatcherStats[station.Name]) {
userProfile.dispatcherStats[station.Name].time = userProfile.dispatcherStats[station.Name].time + time;
} else {
userProfile.dispatcherStats[station.Name] = {
time
}
}
if (Number.isNaN(userProfile.dispatcherStats[station.Name].time)) userProfile.dispatcherStats[station.Name].time = 0;
if (!userProfile.dispatcherTime) userProfile.dispatcherTime = 0;
userProfile.dispatcherTime = userProfile.dispatcherTime + time;
await MProfile.findOneAndUpdate({ id: userProfile.id }, { dispatcherStats: userProfile.dispatcherStats, dispatcherTime: userProfile.dispatcherTime })
}
MLog.create({
id: v4(),
userSteamId: player.steamid,
userAvatar: player.avatarfull,
userUsername: player.personaname,
joinedDate: joinedAt,
leftDate: date.getTime(),
stationName: station.Name,
stationShort: station.Prefix,
server: server.ServerCode
});
})
}
}

View File

@ -1,61 +0,0 @@
import { Server, Station, Train } from '@simrail/types';
import { MLog } from '../mongo/logs.js';
import { IPlayer } from '../types/player.js';
import { SimrailClientEvents } from '../util/SimrailClient.js';
import { v4 } from 'uuid';
import { getVehicle } from '../util/contants.js';
import { MProfile } from '../mongo/profile.js';
import { MTrainLog } from '../mongo/trainLogs.js';
export class TrainsModule {
public static load() {
client.on(SimrailClientEvents.TrainLeft, async (server: Server, train: Train, player: IPlayer, joinedAt: number, leftAt: number, points: number, distance: number, vehicle: string) => {
if (distance) {
const time = joinedAt ? (leftAt - joinedAt) : 0;
const userProfile = await MProfile.findOne({ steam: player.steamid }) ?? await MProfile.create({ steam: player.steamid, id: v4(), steamName: player.personaname });
const vehicleName = getVehicle(vehicle) ?? vehicle;
if (!userProfile.trainStats) userProfile.trainStats = {};
if (userProfile.trainStats[vehicleName]) {
userProfile.trainStats[vehicleName].distance = userProfile.trainStats[vehicleName].distance + distance;
userProfile.trainStats[vehicleName].score = userProfile.trainStats[vehicleName].score + points;
userProfile.trainStats[vehicleName].time = userProfile.trainStats[vehicleName].time + time;
} else {
userProfile.trainStats[vehicleName] = {
distance, score: points, time
}
}
if (!userProfile.trainTime) userProfile.trainTime = 0;
userProfile.trainTime = userProfile.trainTime + time;
if (!userProfile.trainPoints) userProfile.trainPoints = 0;
userProfile.trainPoints = userProfile.trainPoints + points;
if (!userProfile.trainDistance) userProfile.trainDistance = 0;
userProfile.trainDistance = userProfile.trainDistance + distance;
await MProfile.findOneAndUpdate({ id: userProfile.id }, { trainStats: userProfile.trainStats, trainTime: userProfile.trainTime, trainPoints: userProfile.trainPoints, trainDistance: userProfile.trainDistance });
}
MTrainLog.create({
id: v4(),
userSteamId: player.steamid,
userAvatar: player.avatarfull,
userUsername: player.personaname,
joinedDate: joinedAt,
leftDate: leftAt,
trainNumber: train.TrainNoLocal,
server: server.ServerCode,
distance, points,
trainName: train.TrainName
});
})
}
}

View File

@ -1,236 +0,0 @@
import { EventEmitter } from 'node:events';
import { IPlayer } from '../types/player.js';
import { PlayerUtil } from './PlayerUtil.js';
import { Station, ApiResponse, Server, Train } from '@simrail/types';
export enum SimrailClientEvents {
StationJoined = 'stationJoined',
StationLeft = 'stationLeft',
TrainJoined = 'trainJoined',
TrainLeft = 'trainLeft',
}
export declare interface SimrailClient {
on(event: SimrailClientEvents.StationJoined, listener: (server: Server, station: Station, player: IPlayer) => void): this;
on(event: SimrailClientEvents.StationLeft, listener: (server: Server, station: Station, player: IPlayer, joinedAt: number) => void): this;
on(event: SimrailClientEvents.TrainJoined, listener: (server: Server, train: Train, player: IPlayer, startDistance: number) => void): this;
on(event: SimrailClientEvents.TrainLeft, listener: (server: Server, train: Train, player: IPlayer, joinedAt: number, leftAt: number, points: number, distance: number, vehicle: string) => void): this;
//on(event: string, listener: Function): this;
}
export type OccupiedStation = {
SteamId: string;
JoinedAt: number;
}
export type OccupiedTrain = {
SteamId: string;
JoinedAt: number;
StartPlayerDistance: number;
StartPlayerPoints: number;
}
export class SimrailClient extends EventEmitter {
public stations: Record<Server['ServerCode'], Station[]> = {};
public stationsOccupied: Record<Server['ServerCode'], Record<string, OccupiedStation | null>> = {};
public trains: Record<Server['ServerCode'], Train[]> = {};
public trainsOccupied: Record<Server['ServerCode'], Record<string, OccupiedTrain | null>> = {};
public constructor() {
super();
this.setup();
setTimeout(() => setInterval(() => this.update(), 500), 1000)
}
public getStation(server: Server['ServerCode'], name: string) {
if (!this.stationsOccupied[server] || !this.stationsOccupied[server][name]) return null;
const player = PlayerUtil.getPlayer(this.stationsOccupied[server][name]?.SteamId ?? "");
return { player, joinedAt: this.stationsOccupied[name].joinedAt };
}
public getTrain(server: Server['ServerCode'], name: string) {
if (!this.trainsOccupied[server] || !this.trainsOccupied[server][name]) return null;
const player = PlayerUtil.getPlayer(this.trainsOccupied[server][name]?.SteamId ?? "");
return { player, joinedAt: this.trainsOccupied[server][name]?.JoinedAt, startPlayerDistance: this.trainsOccupied[server][name]?.StartPlayerDistance };
}
private async setup() {
console.log(Date.now() - (Number(await redis.get('last_updated'))));
if (!await redis.get('last_updated')) {
await redis.json.set('trains_occupied', '$', {});
await redis.json.set('trains', '$', []);
await redis.json.set('stations', '$', []);
await redis.json.set('stations_occupied', '$', {});
}
const lastUpdated = Date.now() - (Number(await redis.get('last_updated')) ?? 0);
if (lastUpdated > 300_000) {
console.log('REDIS: last updated more than > 5 mins');
await redis.json.set('trains_occupied', '$', {});
await redis.json.set('trains', '$', []);
await redis.json.set('stations', '$', []);
await redis.json.set('stations_occupied', '$', {});
}
if (!await redis.json.get('stations'))
redis.json.set('stations', '$', []);
if (!await redis.json.get('trains'))
redis.json.set('trains', '$', []);
if (!await redis.json.get('trains_occupied'))
redis.json.set('trains_occupied', '$', {});
if (!await redis.json.get('stations_occupied'))
redis.json.set('stations_occupied', '$', {});
this.stations = (await redis.json.get('stations') as unknown as SimrailClient['stations']);
this.stationsOccupied = (await redis.json.get('stations_occupied') as unknown as SimrailClient['stationsOccupied']);
this.trains = (await redis.json.get('trains') as unknown as SimrailClient['trains']);
this.trainsOccupied = (await redis.json.get('trains_occupied') as unknown as SimrailClient['trainsOccupied']);
redis.set('last_updated', Date.now().toString());
}
private async processStation(server: Server, stations: ApiResponse<Station>) {
if (stations.result) {
if (!this.stations[server.ServerCode]) this.stations[server.ServerCode] = [];
if (!this.stationsOccupied[server.ServerCode]) this.stationsOccupied[server.ServerCode] = {};
if (!this.stations[server.ServerCode].length) {
this.stations[server.ServerCode] = stations.data;
redis.json.set('stations', '$', this.stations);
redis.set('last_updated', Date.now().toString());
}
stations.data.forEach(async (x) => {
const data = this.stations[server.ServerCode].find(y => y.Name === x.Name);
if (!data) return;
if (data.DispatchedBy[0]?.SteamId !== x.DispatchedBy[0]?.SteamId) {
if (!data.DispatchedBy[0]?.SteamId) {
// join
const date = new Date();
const player = await PlayerUtil.getPlayer(x.DispatchedBy[0]?.SteamId);
this.emit(SimrailClientEvents.StationJoined, server, x, player);
this.stationsOccupied[server.ServerCode][data.Prefix] = {
SteamId: x.DispatchedBy[0]?.SteamId,
JoinedAt: date.getTime()
};
return;
}
// leave
const player = await PlayerUtil.getPlayer(data.DispatchedBy[0]?.SteamId)
this.emit(SimrailClientEvents.StationLeft, server, x, player, this.stationsOccupied[server.ServerCode][data.Prefix]?.JoinedAt);
delete this.stationsOccupied[server.ServerCode][data.Prefix];
}
})
redis.json.set('stations_occupied', '$', this.stationsOccupied);
this.stations[server.ServerCode] = stations.data;
redis.json.set('stations', '$', this.stations);
redis.set('last_updated', Date.now().toString());
}
}
private async processTrain(server: Server, trains: ApiResponse<Train>) {
if (trains.result) {
if (!this.trains[server.ServerCode]) this.trains[server.ServerCode] = [];
if (!this.trainsOccupied[server.ServerCode]) this.trainsOccupied[server.ServerCode] = {};
if (!this.trains[server.ServerCode].length) {
this.trains[server.ServerCode] = trains.data;
redis.json.set('trains', '$', this.trains);
redis.set('last_updated', Date.now().toString());
return;
}
trains.data.forEach(async (x) => {
const data = this.trains[server.ServerCode].find(y => y.id === x.id);
if (!data) return;
if (data.TrainData.ControlledBySteamID !== x.TrainData.ControlledBySteamID) {
if (!data.TrainData.ControlledBySteamID) {
if (!x.TrainData.ControlledBySteamID) return;
// join
const date = new Date();
const player = await PlayerUtil.getPlayer(x.TrainData.ControlledBySteamID!);
const playerStats = await PlayerUtil.getPlayerStats(x.TrainData.ControlledBySteamID!);
this.emit(SimrailClientEvents.TrainJoined, server, x, player, playerStats?.stats.find(x => x.name === 'DISTANCE_M')?.value);
this.trainsOccupied[server.ServerCode][x.TrainNoLocal] = {
SteamId: x.TrainData.ControlledBySteamID!,
JoinedAt: date.getTime(),
StartPlayerDistance: playerStats?.stats.find(x => x.name === 'DISTANCE_M')?.value!,
StartPlayerPoints: playerStats?.stats.find(x => x.name === "SCORE")?.value!,
};
return;
}
if (!data.TrainData.ControlledBySteamID) return;
const date = new Date();
const player = await PlayerUtil.getPlayer(data.TrainData.ControlledBySteamID!);
const playerId = data.TrainData.ControlledBySteamID!;
const trainOccupied = this.trainsOccupied[server.ServerCode][data.TrainNoLocal] && JSON.parse(JSON.stringify(this.trainsOccupied[server.ServerCode][data.TrainNoLocal])) || null;
setTimeout(() => {
PlayerUtil.getPlayerStats(playerId).then(playerStats => {
const oldKm = trainOccupied?.StartPlayerDistance ?? 0;
const distance = oldKm ? (playerStats?.stats.find(x => x.name === 'DISTANCE_M')?.value ?? 0) - oldKm : 0;
const oldPoints = trainOccupied?.StartPlayerPoints ?? 0;
const points = oldPoints ? (playerStats?.stats.find(x => x.name === 'SCORE')?.value ?? 0) - oldPoints : 0;
this.emit(SimrailClientEvents.TrainLeft, server, data, player, trainOccupied?.JoinedAt, date.getTime(), points, distance, x.Vehicles[0]);
})
}, 80_000)
delete this.trainsOccupied[server.ServerCode][data.TrainNoLocal];
}
})
this.trains[server.ServerCode] = trains.data;
redis.json.set('trains', '$', this.trains);
redis.json.set('trains_occupied', '$', this.trainsOccupied);
redis.set('last_updated', Date.now().toString());
}
}
private async update() {
const servers = (await fetch('https://panel.simrail.eu:8084/servers-open').then(x => x.json()).catch(x => ({ data: [], result: false })) as ApiResponse<Server>)
.data?.filter(x => x.ServerName.includes('Polski')) ?? []; // no plans to support other servers
if (!servers.length) console.log('SimrailAPI is down')
// TODO: maybe node:worker_threads?
// TODO: check performance
servers.forEach(async (server) => {
const stations = (await fetch('https://panel.simrail.eu:8084/stations-open?serverCode=' + server.ServerCode).then(x => x.json()).catch(() => ({ result: false }))) as ApiResponse<Station>;
const trains = (await fetch('https://panel.simrail.eu:8084/trains-open?serverCode=' + server.ServerCode).then(x => x.json()).catch(() => ({ result: false }))) as ApiResponse<Train>;
this.processStation(server, stations);
this.processTrain(server, trains);
});
}
}