diff --git a/package-lock.json b/package-lock.json
index 4b143da..145cade 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index d9e9d6d..57be037 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/readme.md b/readme.md
index 0f10d64..acbbf5c 100644
--- a/readme.md
+++ b/readme.md
@@ -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ć?
diff --git a/src/http/api.ts b/src/http/api.ts
index 6970714..87fc996 100644
--- a/src/http/api.ts
+++ b/src/http/api.ts
@@ -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', {
diff --git a/src/http/views/details.ejs b/src/http/views/details.ejs
index 20d430a..2e15fe1 100644
--- a/src/http/views/details.ejs
+++ b/src/http/views/details.ejs
@@ -44,8 +44,8 @@
@@ -55,8 +55,10 @@
Stacja: <%= record.stationName %>
-
Data wejścia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%=
- dayjs(record.joinedDate).fromNow() %>)
+
Serwer: <%= record.server.toUpperCase() %>
+
+
Data wejścia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
+ record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)
Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow()
%>)
@@ -65,8 +67,9 @@
;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 %>/
diff --git a/src/http/views/index.ejs b/src/http/views/index.ejs
index 8d41ea2..79a1377 100644
--- a/src/http/views/index.ejs
+++ b/src/http/views/index.ejs
@@ -22,33 +22,45 @@
-
Wyszukaj posterunek lub osobe
+
Wyszukaj posterunek, osobe lub serwer
Ostatnie opuszczenia posterunków
- <% records.forEach(record => { %>
+ <% records.forEach(record=> { %>
-
-
- [<%= record.stationShort %>] <%= record.stationName %> - <%= record.userUsername %>
- <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
- Data dołączenia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.joinedDate).fromNow() %>)
- Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow() %>)
+ [
+ <%= record.server.toUpperCase() %>
+ ]
+ <%= record.stationName %>
+ -
+ <%= record.userUsername %>
+
+
+ <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
+
+
+ Data dołączenia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
+ record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)
+ Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%=
+ dayjs(record.leftDate).fromNow() %>)
-
+
- <% }) %>
+ <% }) %>
diff --git a/src/http/views/search.ejs b/src/http/views/search.ejs
index 80dc5e5..568d1bf 100644
--- a/src/http/views/search.ejs
+++ b/src/http/views/search.ejs
@@ -23,16 +23,17 @@
-
Wyszukaj posterunek lub osobe
+
Wyszukaj posterunek, osobe lub serwer
Wyniki wyszukiwania
@@ -41,9 +42,8 @@
<% records.forEach(record=> { %>
-
[
- <%= record.stationShort %>
+ <%= record.server.toUpperCase() %>
]
<%= record.stationName %>
-
@@ -53,8 +53,8 @@
<%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
- Data dołączenia: <%= dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') %> (<%=
- dayjs(record.joinedDate).fromNow() %>)
+ Data dołączenia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' %> (<%=
+ record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)
Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%=
dayjs(record.leftDate).fromNow() %>)
@@ -70,9 +70,9 @@
location.href = '/search?q=' + document.getElementById('search').value
}
document.getElementById('search').addEventListener("keyup", (event) => {
- if (event.key === "Enter")
+ if (event.key === "Enter")
search();
-
+
});
diff --git a/src/index.ts b/src/index.ts
index 923fb42..8e9df41 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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();
diff --git a/src/modules/stations.ts b/src/modules/stations.ts
index 945ca12..093b166 100644
--- a/src/modules/stations.ts
+++ b/src/modules/stations.ts
@@ -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
});
})
}
diff --git a/src/mongo/logs.ts b/src/mongo/logs.ts
index b813832..9555162 100644
--- a/src/mongo/logs.ts
+++ b/src/mongo/logs.ts
@@ -42,7 +42,7 @@ const schema = new Schema(
},
}
);
-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
diff --git a/src/types/payload.ts b/src/types/payload.ts
deleted file mode 100644
index bab2933..0000000
--- a/src/types/payload.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export type ISimrailPayload = {
- result: boolean;
- description: string;
-}
\ No newline at end of file
diff --git a/src/types/player.ts b/src/types/player.ts
index 2edc712..8331999 100644
--- a/src/types/player.ts
+++ b/src/types/player.ts
@@ -1,3 +1,5 @@
+/* steam api */
+
export type IPlayer = {
steamid: string,
communityvisibilitystate: number,
diff --git a/src/types/station.ts b/src/types/station.ts
deleted file mode 100644
index 946bb64..0000000
--- a/src/types/station.ts
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/src/util/PlayerUtil.ts b/src/util/PlayerUtil.ts
index c595ea8..8bbafc5 100644
--- a/src/util/PlayerUtil.ts
+++ b/src/util/PlayerUtil.ts
@@ -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];
diff --git a/src/util/SimrailClient.ts b/src/util/SimrailClient.ts
index 582cc48..957e0d6 100644
--- a/src/util/SimrailClient.ts
+++ b/src/util/SimrailClient.ts
@@ -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 = {};
+ public stations: Record = {};
+ public stationsOccupied: Record> = {};
public constructor() {
super();
@@ -27,13 +31,13 @@ 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 };
}
-
+
private async setup() {
if (!await redis.json.get('stations'))
redis.json.set('stations', '$', []);
@@ -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);
+ 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)
+ .data?.filter(x => x.ServerName.includes('Polski')) ?? []; // no plans to support other servers
+
+ // 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;
+ if (!stations.result) return;
- if (!this.stations.length) {
- this.stations = servers.data;
- redis.json.set('list', '$', this.stations);
- return
- }
-
-
- servers.data.forEach(async (x) => {
- const data = this.stations.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());
-
- this.emit(SimrailClientEvents.StationJoined, x, player);
- this.stationsOccupied[data.Prefix] = {
- steamId: x.DispatchedBy[0]?.SteamId.toString(),
- joinedAt: date.getTime()
- }
- redis.json.set('stations_occupied', '$', this.stationsOccupied);
+ 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;
}
- const player = await PlayerUtil.getPlayer(data.DispatchedBy[0]?.SteamId.toString())
+ stations.data.forEach(async (x) => {
+ const data = this.stations[server.ServerCode].find(y => y.Name === x.Name);
+ if (!data) return;
- this.emit(SimrailClientEvents.StationLeft, x, player, this.stationsOccupied[data.Prefix]?.joinedAt);
- delete this.stationsOccupied[data.Prefix];
- redis.json.set('stations_occupied', '$', this.stationsOccupied);
- }
- })
+ 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()
+ };
+ redis.json.set('stations_occupied', '$', this.stationsOccupied);
+
+ 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 = servers.data;
- redis.json.set('stations', '$', this.stations);
+ this.stations[server.ServerCode] = stations.data;
+ redis.json.set('stations', '$', this.stations);
+ });
}
}
\ No newline at end of file