forked from simrail/simrail.pro
v2.0.0-alpha
This commit is contained in:
parent
56f960128e
commit
fec84ae109
15
package-lock.json
generated
15
package-lock.json
generated
@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "play-history",
|
"name": "simrail-logs",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "play-history",
|
"name": "simrail-logs",
|
||||||
"version": "1.0.0",
|
"license": "AGPL-3.0-only",
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simrail/types": "^0.0.4",
|
"@simrail/types": "^0.0.4",
|
||||||
"@types/mongoose": "^5.11.97",
|
"@types/mongoose": "^5.11.97",
|
||||||
@ -16,7 +15,8 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"mongoose": "^8.5.1",
|
"mongoose": "^8.5.1",
|
||||||
"redis": "^4.6.15",
|
"redis": "^4.6.15",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0",
|
||||||
|
"wildcard-match": "^5.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
@ -1598,6 +1598,11 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wildcard-match": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-a95hPUk+BNzSGLntNXYxsjz2Hooi5oL7xOfJR6CKwSsSALh7vUNuTlzsrZowtYy38JNduYFRVhFv19ocqNOZlg=="
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"mongoose": "^8.5.1",
|
"mongoose": "^8.5.1",
|
||||||
"redis": "^4.6.15",
|
"redis": "^4.6.15",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0",
|
||||||
|
"wildcard-match": "^5.1.3"
|
||||||
},
|
},
|
||||||
"typings": "types/typings.d.ts"
|
"typings": "types/typings.d.ts"
|
||||||
}
|
}
|
||||||
|
16
src/index.ts
16
src/index.ts
@ -6,7 +6,8 @@ import { StationsModule } from './modules/stations.js';
|
|||||||
import { ApiModule } from './http/server.js';
|
import { ApiModule } from './http/server.js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { IPlayer } from './types/player.js';
|
import { IPlayer } from './types/player.js';
|
||||||
import { Station, Server } from '@simrail/types';
|
import { Station, Server, Train } from '@simrail/types';
|
||||||
|
import { TrainsModule } from './modules/trains.js';
|
||||||
|
|
||||||
|
|
||||||
; (async () => {
|
; (async () => {
|
||||||
@ -23,14 +24,25 @@ import { Station, Server } from '@simrail/types';
|
|||||||
global.client = new SimrailClient();
|
global.client = new SimrailClient();
|
||||||
|
|
||||||
client.on(SimrailClientEvents.StationJoined, (server: Server, station: Station, player: IPlayer) => {
|
client.on(SimrailClientEvents.StationJoined, (server: Server, station: Station, player: IPlayer) => {
|
||||||
console.log(`${server.ServerCode} |${station.Name} | ${player.personaname} joined`);
|
console.log(`${server.ServerCode} | ${station.Name} | ${player.personaname} joined`);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on(SimrailClientEvents.StationLeft, (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
|
client.on(SimrailClientEvents.StationLeft, (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
|
||||||
console.log(`${server.ServerCode} | ${station.Name} | ${player.personaname} left. | ${joinedAt ? dayjs(joinedAt).fromNow() : 'no time data.'}`);
|
console.log(`${server.ServerCode} | ${station.Name} | ${player.personaname} left. | ${joinedAt ? dayjs(joinedAt).fromNow() : 'no time data.'}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
client.on(SimrailClientEvents.TrainLeft, (server: Server, train: Train, player: IPlayer, joinedAt: number, leftAt: number, points: number, distance: number, vehicle: string) => {
|
||||||
|
console.log(`${server.ServerCode} | ${train.TrainName} | ${player.personaname} left. | ${joinedAt ? dayjs(joinedAt).fromNow() : 'no time data.'} |
|
||||||
|
${vehicle} | ${distance / 1000} | ${points}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(SimrailClientEvents.TrainJoined, (server: Server, train: Train, player: IPlayer, start: number) => {
|
||||||
|
console.log(`${server.ServerCode} | ${train.TrainName} | ${player.personaname} joined | ${start}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
StationsModule.load();
|
StationsModule.load();
|
||||||
|
TrainsModule.load();
|
||||||
ApiModule.load();
|
ApiModule.load();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -3,12 +3,33 @@ import { MLog } from '../mongo/logs.js';
|
|||||||
import { IPlayer } from '../types/player.js';
|
import { IPlayer } from '../types/player.js';
|
||||||
import { SimrailClientEvents } from '../util/SimrailClient.js';
|
import { SimrailClientEvents } from '../util/SimrailClient.js';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
import { MProfile } from '../mongo/profile.js';
|
||||||
|
import { PlayerUtil } from '../util/PlayerUtil.js';
|
||||||
|
|
||||||
export class StationsModule {
|
export class StationsModule {
|
||||||
public static load() {
|
public static load() {
|
||||||
|
|
||||||
client.on(SimrailClientEvents.StationLeft, (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
|
client.on(SimrailClientEvents.StationLeft, async (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
|
||||||
|
const stats = await PlayerUtil.getPlayerStats(player.steamid);
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
if (stats) {
|
||||||
|
const time = date.getTime() - joinedAt;
|
||||||
|
|
||||||
|
const userProfile = await MProfile.findOne({ steam: player.steamid }) ?? await MProfile.create({ steam: player.steamid, id: v4() });
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(userProfile.dispatcherStats);
|
||||||
|
|
||||||
|
console.log(await MProfile.findOneAndUpdate({ id: userProfile.id }, { dispatcherStats: userProfile.dispatcherStats }))
|
||||||
|
}
|
||||||
|
|
||||||
MLog.create({
|
MLog.create({
|
||||||
id: v4(),
|
id: v4(),
|
||||||
|
@ -21,8 +21,48 @@ export type IPlayer = {
|
|||||||
locstatecode: string
|
locstatecode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IPlayerStats = {
|
||||||
|
"steamID": string,
|
||||||
|
"gameName": string,
|
||||||
|
"stats": [
|
||||||
|
{
|
||||||
|
"name": "SCORE",
|
||||||
|
"value": number
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DISPATCHER_TIME",
|
||||||
|
"value": number
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DISTANCE_M",
|
||||||
|
"value": number
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"achievements": [
|
||||||
|
{
|
||||||
|
"name": "FINISH_MISSION",
|
||||||
|
"achieved": 0 | 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FINISH_ON_TIME",
|
||||||
|
"achieved": 0 | 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FINISH_NIGHT",
|
||||||
|
"achieved": 0 | 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export type IPlayerPayload = {
|
export type IPlayerPayload = {
|
||||||
response: {
|
response: {
|
||||||
players: IPlayer[]
|
players: IPlayer[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IPlayerStatsPayload = {
|
||||||
|
playerstats: IPlayerStats
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IPlayerPayload } from '../types/player.js';
|
import { IPlayerPayload, IPlayerStatsPayload } from '../types/player.js';
|
||||||
|
|
||||||
const STEAM_API_KEY = process.env.STEAM_APIKEY;
|
const STEAM_API_KEY = process.env.STEAM_APIKEY;
|
||||||
|
|
||||||
@ -8,4 +8,10 @@ export class PlayerUtil {
|
|||||||
if (!data.response.players) return;
|
if (!data.response.players) return;
|
||||||
return data.response.players[0];
|
return data.response.players[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getPlayerStats(steamId: string) {
|
||||||
|
const data = (await fetch(`http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=1422130&key=${STEAM_API_KEY}&steamid=${steamId}`).then(x => x.json())) as IPlayerStatsPayload;
|
||||||
|
if (!data.playerstats?.stats) return;
|
||||||
|
return data.playerstats;
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,17 +2,24 @@ import { EventEmitter } from 'node:events';
|
|||||||
|
|
||||||
import { IPlayer } from '../types/player.js';
|
import { IPlayer } from '../types/player.js';
|
||||||
import { PlayerUtil } from './PlayerUtil.js';
|
import { PlayerUtil } from './PlayerUtil.js';
|
||||||
import { Station, ApiResponse, Server } from '@simrail/types';
|
import { Station, ApiResponse, Server, Train } from '@simrail/types';
|
||||||
|
|
||||||
export enum SimrailClientEvents {
|
export enum SimrailClientEvents {
|
||||||
StationJoined = 'stationJoined',
|
StationJoined = 'stationJoined',
|
||||||
StationLeft = 'stationLeft',
|
StationLeft = 'stationLeft',
|
||||||
|
TrainJoined = 'trainJoined',
|
||||||
|
TrainLeft = 'trainLeft',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface SimrailClient {
|
export declare interface SimrailClient {
|
||||||
on(event: SimrailClientEvents.StationJoined, listener: (server: Server, station: Station, player: IPlayer) => void): this;
|
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.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;
|
//on(event: string, listener: Function): this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,10 +28,20 @@ export type OccupiedStation = {
|
|||||||
JoinedAt: number;
|
JoinedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OccupiedTrain = {
|
||||||
|
SteamId: string;
|
||||||
|
JoinedAt: number;
|
||||||
|
StartPlayerDistance: number;
|
||||||
|
StartPlayerPoints: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class SimrailClient extends EventEmitter {
|
export class SimrailClient extends EventEmitter {
|
||||||
public stations: Record<Server['ServerCode'], Station[]> = {};
|
public stations: Record<Server['ServerCode'], Station[]> = {};
|
||||||
public stationsOccupied: Record<Server['ServerCode'], Record<string, OccupiedStation | null>> = {};
|
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() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
this.setup();
|
this.setup();
|
||||||
@ -37,16 +54,28 @@ export class SimrailClient extends EventEmitter {
|
|||||||
return { player, joinedAt: this.stationsOccupied[name].joinedAt };
|
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() {
|
private async setup() {
|
||||||
if (!await redis.json.get('stations'))
|
if (!await redis.json.get('stations'))
|
||||||
redis.json.set('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'))
|
if (!await redis.json.get('stations_occupied'))
|
||||||
redis.json.set('stations_occupied', '$', {});
|
redis.json.set('stations_occupied', '$', {});
|
||||||
|
|
||||||
this.stations = (await redis.json.get('stations') as unknown as SimrailClient['stations']);
|
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.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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -59,8 +88,8 @@ export class SimrailClient extends EventEmitter {
|
|||||||
// TODO: check performance
|
// TODO: check performance
|
||||||
servers.forEach(async (server) => {
|
servers.forEach(async (server) => {
|
||||||
const stations = (await fetch('https://panel.simrail.eu:8084/stations-open?serverCode=' + server.ServerCode).then(x => x.json())) as ApiResponse<Station>;
|
const stations = (await fetch('https://panel.simrail.eu:8084/stations-open?serverCode=' + server.ServerCode).then(x => x.json())) as ApiResponse<Station>;
|
||||||
if (!stations.result) return;
|
const trains = (await fetch('https://panel.simrail.eu:8084/trains-open?serverCode=' + server.ServerCode).then(x => x.json())) as ApiResponse<Train>;
|
||||||
|
if (stations.result) {
|
||||||
if (!this.stations[server.ServerCode]) this.stations[server.ServerCode] = [];
|
if (!this.stations[server.ServerCode]) this.stations[server.ServerCode] = [];
|
||||||
if (!this.stationsOccupied[server.ServerCode]) this.stationsOccupied[server.ServerCode] = {};
|
if (!this.stationsOccupied[server.ServerCode]) this.stationsOccupied[server.ServerCode] = {};
|
||||||
|
|
||||||
@ -85,7 +114,6 @@ export class SimrailClient extends EventEmitter {
|
|||||||
SteamId: x.DispatchedBy[0]?.SteamId,
|
SteamId: x.DispatchedBy[0]?.SteamId,
|
||||||
JoinedAt: date.getTime()
|
JoinedAt: date.getTime()
|
||||||
};
|
};
|
||||||
redis.json.set('stations_occupied', '$', this.stationsOccupied);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,13 +122,79 @@ export class SimrailClient extends EventEmitter {
|
|||||||
|
|
||||||
this.emit(SimrailClientEvents.StationLeft, server, x, player, this.stationsOccupied[server.ServerCode][data.Prefix]?.JoinedAt);
|
this.emit(SimrailClientEvents.StationLeft, server, x, player, this.stationsOccupied[server.ServerCode][data.Prefix]?.JoinedAt);
|
||||||
delete this.stationsOccupied[server.ServerCode][data.Prefix];
|
delete this.stationsOccupied[server.ServerCode][data.Prefix];
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
redis.json.set('stations_occupied', '$', this.stationsOccupied);
|
redis.json.set('stations_occupied', '$', this.stationsOccupied);
|
||||||
|
|
||||||
|
this.stations[server.ServerCode] = stations.data;
|
||||||
|
redis.json.set('stations', '$', this.stations);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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];
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
const playerStats = await PlayerUtil.getPlayerStats(playerId);
|
||||||
|
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]);
|
||||||
|
}, 60000)
|
||||||
|
delete this.trainsOccupied[server.ServerCode][data.TrainNoLocal];
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
this.stations[server.ServerCode] = stations.data;
|
this.trains[server.ServerCode] = trains.data;
|
||||||
redis.json.set('stations', '$', this.stations);
|
redis.json.set('trains', '$', this.trains);
|
||||||
|
redis.json.set('trains_occupied', '$', this.trainsOccupied);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user