Merge branch 'multiple-stations'

This commit is contained in:
Aleksander Wilczyński 2024-08-04 13:49:38 +02:00
commit 7e6095f541
Signed by untrusted user: alekswilc
GPG Key ID: D4464A248E5F27FE
15 changed files with 161 additions and 114 deletions

16
package-lock.json generated
View File

@ -9,6 +9,8 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@simrail/types": "^0.0.4",
"@types/mongoose": "^5.11.97",
"dayjs": "^1.11.12",
"ejs": "^3.1.10",
"express": "^4.19.2",
@ -85,6 +87,11 @@
"@redis/client": "^1.0.0"
}
},
"node_modules/@simrail/types": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@simrail/types/-/types-0.0.4.tgz",
"integrity": "sha512-AknM5FP+crERb3m/YtZqCgdFbrwUEwYB4BXfv1z+b7CVSrRsidAnaMIJZw/MhmsGxAmNIqQK7RPhDEx8qNzblA=="
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@ -140,6 +147,15 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
},
"node_modules/@types/mongoose": {
"version": "5.11.97",
"resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz",
"integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==",
"deprecated": "Mongoose publishes its own types, so you do not need to install this package.",
"dependencies": {
"mongoose": "*"
}
},
"node_modules/@types/node": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz",

View File

@ -4,7 +4,8 @@
"main": "index.js",
"scripts": {
"build": "tsc",
"postbuild": "copyfiles --error --up 1 src/http/views/*.* dist"
"postbuild": "copyfiles --error --up 1 src/http/views/*.* dist",
"dev": "npm run build && doppler run node dist"
},
"keywords": [],
"author": "",
@ -19,6 +20,8 @@
},
"type": "module",
"dependencies": {
"@simrail/types": "^0.0.4",
"@types/mongoose": "^5.11.97",
"dayjs": "^1.11.12",
"ejs": "^3.1.10",
"express": "^4.19.2",

View File

@ -5,7 +5,6 @@ Prosty projekt, logujący wyjścia z posterunków.
- Ułatwienie zgłaszania graczy, którzy robią "Hit and Run" (psuje i wychodze z posterunku)
## Dalszy rozwój
- Wybieranie serwerów
- Obsługa pociagów, a nie tylko posterunków
# Jak korzystać?

View File

@ -26,9 +26,37 @@ export class ApiModule {
});
})
const generateSearch = (regex: RegExp) => [
{
stationName: { $regex: regex },
},
{
userUsername: { $regex: regex },
},
{
stationShort: { $regex: regex },
},
{
userSteamId: { $regex: regex },
},
{
server: { $regex: regex },
}
]
app.get('/search', async (req, res) => {
if (!req.query.q) return res.redirect('/');
const records = await MLog.find({ $text: { $search: req.query.q as string } })
const s = req.query.q.toString().split(',').map(x => new RegExp(x, "i"));
const records = await MLog.aggregate([
{
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
}
])
.sort({ leftDate: -1 })
.limit(30)
res.render('search.ejs', {

View File

@ -44,8 +44,8 @@
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs [<span
style="color:hotpink">PL2</span>]</a></h1>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
</header>
<div class="details">
@ -55,8 +55,10 @@
</a></p>
<p>Stacja: <%= record.stationName %>
</p>
<p>Data wejścia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%=
dayjs(record.joinedDate).fromNow() %>)</p>
<p>Serwer: <%= record.server.toUpperCase() %>
</p>
<p>Data wejścia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)</p>
<p>Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow()
%>)</p>
@ -65,8 +67,9 @@
<br />
<code class="clickable" style="white-space: pre-line" onclick="copydata()" id="data">;station: <%= record.stationName %>
;steam: <%= record.userSteamId %>
;server: <%= record.server %>
;name: <%= record.userUsername %>
;joined: <%=dayjs(record.joinedDate).format()%>
;joined: <%=record.joinedDate ? dayjs(record.joinedDate).format() : 'no-data'%>
;left: <%=dayjs(record.leftDate).format()%>
;url: https://simrail.alekswilc.dev/details/<%= record.id %>/
</code>

View File

@ -22,14 +22,16 @@
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs [<span style="color:hotpink">PL2</span>]</a></h1>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
</header>
<h2>Wyszukaj posterunek lub osobe</h2>
<h2>Wyszukaj posterunek, osobe lub serwer</h2>
<div class="container">
<input type="text" id="search">
<button onclick="search()">Szukaj</button>
<p>Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy</p>
</div>
<h2>Ostatnie opuszczenia posterunków</h2>
@ -38,11 +40,21 @@
<% records.forEach(record=> { %>
<li>
<details>
<summary>[<span style="color:lightskyblue"><%= record.stationShort %></span>] <span style="color: lightskyblue"><%= record.stationName %></span> - <span style="color:hotpink"><%= record.userUsername %></span>
<p style="margin-bottom: 0; opacity: 0.5;"><%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %></p></summary>
<p>Data dołączenia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.joinedDate).fromNow() %>)</p>
<p>Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow() %>)</p>
<summary>[<span style="color:lightskyblue">
<%= record.server.toUpperCase() %>
</span>] <span style="color: lightskyblue">
<%= record.stationName %>
</span> - <span style="color:hotpink">
<%= record.userUsername %>
</span>
<p style="margin-bottom: 0; opacity: 0.5;">
<%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
</p>
</summary>
<p>Data dołączenia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)</p>
<p>Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%=
dayjs(record.leftDate).fromNow() %>)</p>
<a href="/details/<%= record.id %>">
<button>Więcej</button>
</a>

View File

@ -23,16 +23,17 @@
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs [<span
style="color:hotpink">PL2</span>]</a></h1>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
</header>
<h2>Wyszukaj posterunek lub osobe</h2>
<h2>Wyszukaj posterunek, osobe lub serwer</h2>
<div class="container">
<input type="text" id="search" value="<%=q%>">
<button onclick="search()">Szukaj</button>
<p>Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy</p>
</div>
<h2>Wyniki wyszukiwania</h2>
@ -41,9 +42,8 @@
<% records.forEach(record=> { %>
<li>
<details>
<summary>[<span style="color:lightskyblue">
<%= record.stationShort %>
<%= record.server.toUpperCase() %>
</span>] <span style="color: lightskyblue">
<%= record.stationName %>
</span> - <span style="color:hotpink">
@ -53,8 +53,8 @@
<%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
</p>
</summary>
<p>Data dołączenia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%=
dayjs(record.joinedDate).fromNow() %>)</p>
<p>Data dołączenia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)</p>
<p>Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%=
dayjs(record.leftDate).fromNow() %>)</p>
<a href="/details/<%= record.id %>">

View File

@ -5,8 +5,8 @@ import dayjs from 'dayjs';
import { StationsModule } from './modules/stations.js';
import { ApiModule } from './http/api.js';
import mongoose from 'mongoose';
import { IStation } from './types/station.js';
import { IPlayer } from './types/player.js';
import { Station, Server } from '@simrail/types';
; (async () => {
@ -22,12 +22,12 @@ import { IPlayer } from './types/player.js';
console.log('MongoDB connected');
global.client = new SimrailClient();
client.on(SimrailClientEvents.StationJoined, (station: IStation, player: IPlayer) => {
console.log(`${station.Name} | ${player.personaname} joined`);
client.on(SimrailClientEvents.StationJoined, (server: Server, station: Station, player: IPlayer) => {
console.log(`${server.ServerCode} |${station.Name} | ${player.personaname} joined`);
});
client.on(SimrailClientEvents.StationLeft, (station: IStation, player: IPlayer, joinedAt: number) => {
console.log(`${station.Name} | ${player.personaname} left. | ${joinedAt ? dayjs(joinedAt).fromNow() : 'no time data.'}`);
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.'}`);
});
StationsModule.load();

View File

@ -1,13 +1,13 @@
import { Server, Station } from '@simrail/types';
import { MLog } from '../mongo/logs.js';
import { IPlayer } from '../types/player.js';
import { IStation } from '../types/station.js';
import { SimrailClientEvents } from '../util/SimrailClient.js';
import { v4 } from 'uuid';
export class StationsModule {
public static load() {
client.on(SimrailClientEvents.StationLeft, (station: IStation, player: IPlayer, joinedAt: number) => {
client.on(SimrailClientEvents.StationLeft, (server: Server, station: Station, player: IPlayer, joinedAt: number) => {
const date = new Date();
MLog.create({
@ -19,7 +19,7 @@ export class StationsModule {
leftDate: date.getTime(),
stationName: station.Name,
stationShort: station.Prefix,
server: 'pl2'
server: server.ServerCode
});
})
}

View File

@ -42,7 +42,7 @@ const schema = new Schema<ILog>(
},
}
);
schema.index({ stationName: 'text', userUsername: 'text', stationShort: 'text', userSteamId: 'text' })
schema.index({ stationName: 'text', userUsername: 'text', stationShort: 'text', userSteamId: 'text', server: 'text' })
export type TMLog = Model<ILog>

View File

@ -1,4 +0,0 @@
export type ISimrailPayload = {
result: boolean;
description: string;
}

View File

@ -1,3 +1,5 @@
/* steam api */
export type IPlayer = {
steamid: string,
communityvisibilitystate: number,

View File

@ -1,23 +0,0 @@
import { ISimrailPayload } from './payload.js';
export type IStation = {
Name: string;
Prefix: string;
DifficultyLevel: number;
Latititude: number;
Longitude: number;
MainImageURL: string;
AdditionalImage1URL: string;
AdditionalImage2URL: string;
DispatchedBy: IStationDispatch[];
}
export type IStationDispatch = {
ServerCode: string,
SteamId: number
}
export type IStationPayload = {
data: IStation[];
count: number;
} & ISimrailPayload;

View File

@ -3,7 +3,7 @@ import { IPlayerPayload } from '../types/player.js';
const STEAM_API_KEY = process.env.STEAM_APIKEY;
export class PlayerUtil {
public static async getPlayer(steamId: string) {
public static async getPlayer(steamId: number | string) {
const data = (await fetch(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=${STEAM_API_KEY}&format=json&steamids=${steamId}`).then(x => x.json())) as IPlayerPayload;
if (!data.response.players) return;
return data.response.players[0];

View File

@ -1,25 +1,29 @@
import { EventEmitter } from 'node:events';
import { IStation, IStationPayload } from '../types/station.js';
import { IPlayer } from '../types/player.js';
import { PlayerUtil } from './PlayerUtil.js';
import { Station, ApiResponse, Server } from '@simrail/types';
export enum SimrailClientEvents {
StationJoined = 'stationJoined',
StationLeft = 'stationLeft',
}
export declare interface SimrailClient {
on(event: SimrailClientEvents.StationJoined, listener: (station: IStation, player: IPlayer) => void): this;
on(event: SimrailClientEvents.StationLeft, listener: (station: IStation, player: IPlayer, joinedAt: number) => 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: string, listener: Function): this;
//on(event: string, listener: Function): this;
}
export type OccupiedStation = {
SteamId: string;
JoinedAt: number;
}
export class SimrailClient extends EventEmitter {
public stations: IStation[] = [];
public stationsOccupied: Record<string, { steamId: string; joinedAt: number } | null> = {};
public stations: Record<Server['ServerCode'], Station[]> = {};
public stationsOccupied: Record<Server['ServerCode'], Record<string, OccupiedStation | null>> = {};
public constructor() {
super();
@ -27,9 +31,9 @@ export class SimrailClient extends EventEmitter {
setTimeout(() => setInterval(() => this.update(), 500), 1000)
}
public getStation(name: string) {
if (!this.stationsOccupied[name]) return null;
const player = PlayerUtil.getPlayer(this.stationsOccupied[name].steamId);
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 };
}
@ -41,55 +45,62 @@ export class SimrailClient extends EventEmitter {
if (!await redis.json.get('stations_occupied'))
redis.json.set('stations_occupied', '$', {});
this.stations = (await redis.json.get('stations') as unknown as IStation[]);
this.stationsOccupied = (await redis.json.get('stations_occupied') as unknown as Record<string, { steamId: string; joinedAt: number } | null>);
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']);
}
private async update() {
const servers = (await fetch('https://panel.simrail.eu:8084/stations-open?serverCode=pl2').then(x => x.json())) as IStationPayload;
if (!servers.result) return
const servers = (await fetch('https://panel.simrail.eu:8084/servers-open').then(x => x.json()) as ApiResponse<Server>)
.data?.filter(x => x.ServerName.includes('Polski')) ?? []; // no plans to support other servers
if (!this.stations.length) {
this.stations = servers.data;
redis.json.set('list', '$', this.stations);
return
// 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())) as ApiResponse<Station>;
if (!stations.result) return;
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);
return;
}
servers.data.forEach(async (x) => {
const data = this.stations.find(y => y.Name === x.Name);
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.toString());
const player = await PlayerUtil.getPlayer(x.DispatchedBy[0]?.SteamId);
this.emit(SimrailClientEvents.StationJoined, x, player);
this.stationsOccupied[data.Prefix] = {
steamId: x.DispatchedBy[0]?.SteamId.toString(),
joinedAt: date.getTime()
}
this.emit(SimrailClientEvents.StationJoined, server, x, player);
this.stationsOccupied[server.ServerCode][data.Prefix] = {
SteamId: x.DispatchedBy[0]?.SteamId,
JoinedAt: date.getTime()
};
redis.json.set('stations_occupied', '$', this.stationsOccupied);
return;
}
// leave
const player = await PlayerUtil.getPlayer(data.DispatchedBy[0]?.SteamId)
const player = await PlayerUtil.getPlayer(data.DispatchedBy[0]?.SteamId.toString())
this.emit(SimrailClientEvents.StationLeft, x, player, this.stationsOccupied[data.Prefix]?.joinedAt);
delete this.stationsOccupied[data.Prefix];
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 = servers.data;
this.stations[server.ServerCode] = stations.data;
redis.json.set('stations', '$', this.stations);
});
}
}