cs2-log-parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/lib/assertions.d.ts +3 -0
- package/lib/assertions.js +13 -0
- package/lib/helpers.d.ts +1 -0
- package/lib/helpers.js +18 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +1 -0
- package/lib/models.d.ts +80 -0
- package/lib/models.js +133 -0
- package/lib/parser.d.ts +9 -0
- package/lib/parser.js +74 -0
- package/lib/parsers/assisted.d.ts +8 -0
- package/lib/parsers/assisted.js +15 -0
- package/lib/parsers/attacked.d.ts +13 -0
- package/lib/parsers/attacked.js +25 -0
- package/lib/parsers/blinded.d.ts +9 -0
- package/lib/parsers/blinded.js +15 -0
- package/lib/parsers/connection.d.ts +20 -0
- package/lib/parsers/connection.js +34 -0
- package/lib/parsers/entity-triggered.d.ts +10 -0
- package/lib/parsers/entity-triggered.js +17 -0
- package/lib/parsers/index.d.ts +17 -0
- package/lib/parsers/index.js +17 -0
- package/lib/parsers/killed-by-bomb.d.ts +4 -0
- package/lib/parsers/killed-by-bomb.js +13 -0
- package/lib/parsers/killed.d.ts +9 -0
- package/lib/parsers/killed.js +22 -0
- package/lib/parsers/left-buyzone.d.ts +7 -0
- package/lib/parsers/left-buyzone.js +13 -0
- package/lib/parsers/picked-up.d.ts +7 -0
- package/lib/parsers/picked-up.js +13 -0
- package/lib/parsers/purchased.d.ts +7 -0
- package/lib/parsers/purchased.js +13 -0
- package/lib/parsers/rcon.d.ts +7 -0
- package/lib/parsers/rcon.js +13 -0
- package/lib/parsers/say.d.ts +8 -0
- package/lib/parsers/say.js +14 -0
- package/lib/parsers/scored.d.ts +8 -0
- package/lib/parsers/scored.js +14 -0
- package/lib/parsers/suicide.d.ts +7 -0
- package/lib/parsers/suicide.js +16 -0
- package/lib/parsers/switched-team.d.ts +8 -0
- package/lib/parsers/switched-team.js +14 -0
- package/lib/parsers/team-triggered.d.ts +11 -0
- package/lib/parsers/team-triggered.js +15 -0
- package/lib/parsers/threw.d.ts +9 -0
- package/lib/parsers/threw.js +16 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Negezor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# cs2-log-parser
|
|
2
|
+
|
|
3
|
+
A simple 0-dependency library that parses logs from a CS2 server into JavaScript objects, originally based on this fork by [BLAST](https://github.com/blastorg) (available [here](https://github.com/blastorg/srcds-log-parser)), and the original library made by [negezor](https://github.com/negezor) (available [here](https://github.com/negezor/srcds)).
|
|
4
|
+
These libraries however have not been updated in a while, which is the reason for making this one, which has been cleaned up & updated to the latest CS2 logging formats, with some new log parsers added.
|
|
5
|
+
|
|
6
|
+
For a list of the logs this library supports look inside the [tests](/tests/) directory.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i cs2-log-parser
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
### HTTP
|
|
17
|
+
|
|
18
|
+
CS2 supports sending logs over HTTP POST requests by giving it an address using `logaddress_add_http`.
|
|
19
|
+
Here is a simple example using [tinyhttp](https://github.com/tinyhttp/tinyhttp) and [milliparsec](https://github.com/tinyhttp/milliparsec) on how we can handle these on our server:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { App } from "@tinyhttp/app";
|
|
23
|
+
import { text } from "milliparsec";
|
|
24
|
+
import { StatusCodes } from "http-status-codes";
|
|
25
|
+
import parse from "cs2-log-parser";
|
|
26
|
+
|
|
27
|
+
const app = new App();
|
|
28
|
+
|
|
29
|
+
app.post("/cs2_logs", text(), (req, res) => {
|
|
30
|
+
|
|
31
|
+
const logLines = req.body.split("\n");
|
|
32
|
+
|
|
33
|
+
for (const logLine of logLines) {
|
|
34
|
+
|
|
35
|
+
const event = parse(logLine, { format: "http" });
|
|
36
|
+
|
|
37
|
+
if (event === null) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(event);
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
res.sendStatus(StatusCodes.OK);
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app.listen(3000);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Log file
|
|
53
|
+
|
|
54
|
+
CS2 can write logs to a text file by setting `sv_logfile 1`, saved in a directory specified by `sv_logsdir` (defaults to `logs` inside the `game/csgo` folder).
|
|
55
|
+
Here is a simple example on how we can read these using Node's built-in `fs` & `path` modules:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import fs from "fs";
|
|
59
|
+
import path from "path";
|
|
60
|
+
import parse from "cs2-log-parser";
|
|
61
|
+
|
|
62
|
+
const logFilePath = path.join(
|
|
63
|
+
"C:",
|
|
64
|
+
"Program Files (x86)",
|
|
65
|
+
"Steam",
|
|
66
|
+
"steamapps",
|
|
67
|
+
"common",
|
|
68
|
+
"Counter-Strike Global Offensive",
|
|
69
|
+
"game",
|
|
70
|
+
"csgo",
|
|
71
|
+
"logs",
|
|
72
|
+
"2025_10_08_214757.log"
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const logFileData = fs.readFileSync(logFilePath, "utf-8");
|
|
76
|
+
|
|
77
|
+
const logLines = logFileData.split("\n");
|
|
78
|
+
|
|
79
|
+
for (const logLine of logLines) {
|
|
80
|
+
|
|
81
|
+
// Log file lines begin with "L" & contain whitespace, we clean them up here
|
|
82
|
+
const cleanLogLine = logLine.slice(1).trim();
|
|
83
|
+
|
|
84
|
+
const event = parse(cleanLogLine, { format: "file" });
|
|
85
|
+
|
|
86
|
+
if (!event) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(event);
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Note
|
|
96
|
+
|
|
97
|
+
Some logs require additional ConVars to be set on the server in order to get logged, specifically:
|
|
98
|
+
|
|
99
|
+
- `attacked` needs `mp_logdetail` set to either `1` (to log enemy attacks), `2` (to log team attacks) or `3` (to log both).
|
|
100
|
+
- `picked_up` needs `mp_logdetail_items` set to `true` (or `1`).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function assertValidVector(vectorArray) {
|
|
2
|
+
if (vectorArray.some((n) => isNaN(n))) {
|
|
3
|
+
throw new Error('Vector contains invalid number(s)');
|
|
4
|
+
}
|
|
5
|
+
if (vectorArray.length !== 3) {
|
|
6
|
+
throw new Error(`Vector must have 3 numbers, got ${vectorArray.length}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function assertValidConnectionState(state) {
|
|
10
|
+
if (state !== 'connected' && state !== 'entered' && state !== 'disconnected') {
|
|
11
|
+
throw new Error(`Received unknown connection state, got ${state}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/lib/helpers.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function convertSteamIdTo64Dec(steamId: string): string;
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function convertSteamIdTo64Dec(steamId) {
|
|
2
|
+
// old steam id format: STEAM_1:0:12345
|
|
3
|
+
if (steamId.includes('STEAM')) {
|
|
4
|
+
const steamIdSplit = steamId.split(':');
|
|
5
|
+
let commId = parseInt(steamIdSplit[2]) * 2;
|
|
6
|
+
if (steamIdSplit[1] === '1') {
|
|
7
|
+
commId += 1;
|
|
8
|
+
}
|
|
9
|
+
const newCommId = BigInt(commId) + BigInt(76561197960265728);
|
|
10
|
+
return newCommId.toString();
|
|
11
|
+
}
|
|
12
|
+
// new steam id format: [U:1:230970467]
|
|
13
|
+
const cleanedSteamId = steamId.replaceAll(/\[|\]/g, '');
|
|
14
|
+
const uSteamIdSplit = cleanedSteamId.split(':');
|
|
15
|
+
const commId = BigInt(uSteamIdSplit[2]) + BigInt(76561197960265728);
|
|
16
|
+
return commId.toString();
|
|
17
|
+
}
|
|
18
|
+
;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AssistedEvent, AttackedEvent, BlindedEvent, ConnectionEvent, EntityTriggeredEvent, KilledByBombEvent, KilledEvent, LeftBuyzoneEvent, PickedUpEvent, PurchasedEvent, RconEvent, SayEvent, ScoredEvent, SuicideEvent, SwitchedTeamEvent, TeamTriggeredEvent, ThrewEvent } from './parsers/index.js';
|
|
2
|
+
export { Event, ParseOptions, defaultParsers, parse as default } from './parser.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defaultParsers, parse as default } from './parser.js';
|
package/lib/models.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export declare const baseEntityRe: RegExp;
|
|
2
|
+
export declare const basePlayerRe: RegExp;
|
|
3
|
+
export declare const vectorRe: RegExp;
|
|
4
|
+
export declare const entityRe: RegExp;
|
|
5
|
+
export declare enum Team {
|
|
6
|
+
Unassigned = 0,
|
|
7
|
+
Spectators = 1,
|
|
8
|
+
Terrorists = 2,
|
|
9
|
+
CounterTerrorists = 3,
|
|
10
|
+
Unknown = 999
|
|
11
|
+
}
|
|
12
|
+
export declare const teamMapping: Record<string, Team>;
|
|
13
|
+
export type EntityID = number;
|
|
14
|
+
export type KillModifier = 'attackerblind' | 'attackerinair' | 'headshot' | 'penetrated' | 'throughsmoke' | 'noscope';
|
|
15
|
+
export type SuicideMethod = 'world' | 'hegrenade' | 'inferno';
|
|
16
|
+
export type ConnectionState = 'connected' | 'entered' | 'disconnected';
|
|
17
|
+
export type HitGroup = 'generic' | 'head' | 'chest' | 'stomach' | 'left_arm' | 'right_arm' | 'left_leg' | 'right_leg';
|
|
18
|
+
export type SayTo = 'all' | 'team';
|
|
19
|
+
export type LogTimestampFormat = 'file' | 'http';
|
|
20
|
+
export type Entity = WorldEntity | ConsoleEntity | ChickenEntity | PlayerEntity | BotEntity | MultiplayerPhysicsPropEntity | DynamicPropEntity;
|
|
21
|
+
export type Vector = [number, number, number];
|
|
22
|
+
export interface BaseEvent<T, P> {
|
|
23
|
+
name: T;
|
|
24
|
+
payload: P;
|
|
25
|
+
receivedAt: Date;
|
|
26
|
+
}
|
|
27
|
+
export interface BaseParser<T extends BaseEvent<string, unknown>> {
|
|
28
|
+
name: T['name'];
|
|
29
|
+
patterns: RegExp[];
|
|
30
|
+
parse(groups: Record<string, string>): T['payload'];
|
|
31
|
+
}
|
|
32
|
+
interface BaseEntity {
|
|
33
|
+
type: string;
|
|
34
|
+
}
|
|
35
|
+
interface IdentifiedEntity extends BaseEntity {
|
|
36
|
+
id: EntityID;
|
|
37
|
+
}
|
|
38
|
+
interface PositionedEntity extends IdentifiedEntity {
|
|
39
|
+
position?: Vector;
|
|
40
|
+
}
|
|
41
|
+
export interface MultiplayerPhysicsPropEntity extends PositionedEntity {
|
|
42
|
+
type: 'prop_physics_multiplayer';
|
|
43
|
+
}
|
|
44
|
+
export interface DynamicPropEntity extends PositionedEntity {
|
|
45
|
+
type: 'prop_dynamic';
|
|
46
|
+
}
|
|
47
|
+
export interface WorldEntity extends BaseEntity {
|
|
48
|
+
type: 'world';
|
|
49
|
+
}
|
|
50
|
+
export interface ConsoleEntity extends BaseEntity {
|
|
51
|
+
type: 'console';
|
|
52
|
+
}
|
|
53
|
+
export interface ChickenEntity extends PositionedEntity {
|
|
54
|
+
type: 'chicken';
|
|
55
|
+
id: EntityID;
|
|
56
|
+
}
|
|
57
|
+
export interface BasePlayerEntity extends PositionedEntity {
|
|
58
|
+
id: EntityID;
|
|
59
|
+
name: string;
|
|
60
|
+
position?: Vector;
|
|
61
|
+
team: Team;
|
|
62
|
+
}
|
|
63
|
+
export interface PlayerEntity extends BasePlayerEntity {
|
|
64
|
+
type: 'player';
|
|
65
|
+
steamId3: string;
|
|
66
|
+
steamId64: string;
|
|
67
|
+
}
|
|
68
|
+
export interface BotEntity extends BasePlayerEntity {
|
|
69
|
+
type: 'bot';
|
|
70
|
+
}
|
|
71
|
+
export declare function parseTeam(rawTeam: string): Team;
|
|
72
|
+
export declare function parseKillModifiers(rawModifiers?: string): KillModifier[];
|
|
73
|
+
export declare function parseConnectionState(rawState?: string): ConnectionState;
|
|
74
|
+
export declare function parseHitGroup(rawHitGroup: string): HitGroup;
|
|
75
|
+
export declare function parseEquipmentList(rawEquipmentList: string): string[];
|
|
76
|
+
export declare function parseEntity(rawEntity: string): Entity;
|
|
77
|
+
export declare function parseVector(rawVector?: string): Vector;
|
|
78
|
+
export declare function concatPattern(literals: TemplateStringsArray, ...placeholders: (string | RegExp)[]): RegExp;
|
|
79
|
+
export declare function defineParser<T extends BaseEvent<string, unknown>>(parser: BaseParser<T>): BaseParser<T>;
|
|
80
|
+
export {};
|
package/lib/models.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { assertValidConnectionState, assertValidVector } from './assertions.js';
|
|
2
|
+
import { convertSteamIdTo64Dec } from './helpers.js';
|
|
3
|
+
// ----------------------------
|
|
4
|
+
// Regular expressions
|
|
5
|
+
// ----------------------------
|
|
6
|
+
const baseWorldRe = /World/;
|
|
7
|
+
export const baseEntityRe = /"(?<entityType>chicken|prop_physics_multiplayer|prop_dynamic)<(?<entityId>\d+)>"/;
|
|
8
|
+
export const basePlayerRe = /"(?<name>.+)<(?<entityId>\d*)><(?<steamId>(?:\[U:[01]:\d+\]|STEAM_[0-5]:[01]:\d+|BOT|Console))>(?:<(?<team>[^>]*)>)?"/;
|
|
9
|
+
export const vectorRe = /\[?[-.\d]+ [-.\d]+ [-.\d]+\]?/;
|
|
10
|
+
export const entityRe = /".+<\d*><(?:\[U:[01]:\d+\]|STEAM_[0-5]:[01]:\d+|BOT|Console)>(?:<[^>]*>)?"|"chicken<\d+>"|World/;
|
|
11
|
+
// ----------------------------
|
|
12
|
+
// Enums
|
|
13
|
+
// ----------------------------
|
|
14
|
+
export var Team;
|
|
15
|
+
(function (Team) {
|
|
16
|
+
Team[Team["Unassigned"] = 0] = "Unassigned";
|
|
17
|
+
Team[Team["Spectators"] = 1] = "Spectators";
|
|
18
|
+
Team[Team["Terrorists"] = 2] = "Terrorists";
|
|
19
|
+
Team[Team["CounterTerrorists"] = 3] = "CounterTerrorists";
|
|
20
|
+
Team[Team["Unknown"] = 999] = "Unknown";
|
|
21
|
+
})(Team || (Team = {}));
|
|
22
|
+
// ----------------------------
|
|
23
|
+
// Mappings
|
|
24
|
+
// ----------------------------
|
|
25
|
+
export const teamMapping = {
|
|
26
|
+
Unassigned: Team.Unassigned,
|
|
27
|
+
Spectator: Team.Spectators,
|
|
28
|
+
TERRORIST: Team.Terrorists,
|
|
29
|
+
CT: Team.CounterTerrorists
|
|
30
|
+
};
|
|
31
|
+
// ----------------------------
|
|
32
|
+
// Base parsers
|
|
33
|
+
// ----------------------------
|
|
34
|
+
export function parseTeam(rawTeam) {
|
|
35
|
+
const mappedTeam = teamMapping[rawTeam] ?? Team.Unknown;
|
|
36
|
+
return mappedTeam;
|
|
37
|
+
}
|
|
38
|
+
export function parseKillModifiers(rawModifiers) {
|
|
39
|
+
const modifiersArr = rawModifiers?.split(' ') ?? [];
|
|
40
|
+
return modifiersArr;
|
|
41
|
+
}
|
|
42
|
+
export function parseConnectionState(rawState) {
|
|
43
|
+
assertValidConnectionState(rawState);
|
|
44
|
+
return rawState;
|
|
45
|
+
}
|
|
46
|
+
export function parseHitGroup(rawHitGroup) {
|
|
47
|
+
// We don't want spaces in our values, and CS2 returns hitgroups with spaces:
|
|
48
|
+
// ("left leg", "right arm" etc), so we join them with underscores here
|
|
49
|
+
const formattedHitGroup = rawHitGroup.split(' ').join('_');
|
|
50
|
+
return formattedHitGroup;
|
|
51
|
+
}
|
|
52
|
+
export function parseEquipmentList(rawEquipmentList) {
|
|
53
|
+
const equipmentStr = rawEquipmentList.replaceAll('[', '').replaceAll(']', '').trim();
|
|
54
|
+
let equipment;
|
|
55
|
+
if (equipmentStr.length > 0) {
|
|
56
|
+
equipment = equipmentStr.split(' ');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
equipment = [];
|
|
60
|
+
}
|
|
61
|
+
return equipment;
|
|
62
|
+
}
|
|
63
|
+
export function parseEntity(rawEntity) {
|
|
64
|
+
// Test for "world" entity
|
|
65
|
+
if (baseWorldRe.test(rawEntity)) {
|
|
66
|
+
return {
|
|
67
|
+
type: 'world',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Test for base entities (chicken, prop_dynamic & prop_physics_multiplayer)
|
|
71
|
+
if (baseEntityRe.test(rawEntity)) {
|
|
72
|
+
const baseEntityMatch = rawEntity.match(baseEntityRe);
|
|
73
|
+
const entityType = baseEntityMatch[1];
|
|
74
|
+
const entityId = Number(baseEntityMatch[2]);
|
|
75
|
+
if (entityType === 'chicken' || entityType === 'prop_dynamic' || entityType === 'prop_physics_multiplayer') {
|
|
76
|
+
return {
|
|
77
|
+
type: entityType,
|
|
78
|
+
id: entityId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw new Error(`Unknown base entity type "${rawEntity}"`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (basePlayerRe.test(rawEntity)) {
|
|
86
|
+
const { entityId: rawEntityId, steamId, name, team: rawTeam } = rawEntity.match(basePlayerRe).groups;
|
|
87
|
+
// Test for "Console" entity
|
|
88
|
+
if (steamId === 'Console') {
|
|
89
|
+
return {
|
|
90
|
+
type: 'console',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const entityId = Number(rawEntityId);
|
|
94
|
+
const team = parseTeam(rawTeam);
|
|
95
|
+
// Test for BOT entity
|
|
96
|
+
if (steamId === 'BOT') {
|
|
97
|
+
return {
|
|
98
|
+
type: 'bot',
|
|
99
|
+
id: entityId,
|
|
100
|
+
name,
|
|
101
|
+
team,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
type: 'player',
|
|
106
|
+
id: entityId,
|
|
107
|
+
steamId3: steamId,
|
|
108
|
+
steamId64: convertSteamIdTo64Dec(steamId),
|
|
109
|
+
name,
|
|
110
|
+
team,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`Failed to parse entity "${rawEntity}"`);
|
|
114
|
+
}
|
|
115
|
+
export function parseVector(rawVector) {
|
|
116
|
+
const vectorStrings = rawVector?.split(' ') ?? [];
|
|
117
|
+
const vectorNumbers = vectorStrings.map(Number);
|
|
118
|
+
assertValidVector(vectorNumbers);
|
|
119
|
+
return vectorNumbers;
|
|
120
|
+
}
|
|
121
|
+
export function concatPattern(literals, ...placeholders) {
|
|
122
|
+
let result = '';
|
|
123
|
+
for (let i = 0; i < placeholders.length; i += 1) {
|
|
124
|
+
result += literals[i];
|
|
125
|
+
const { [i]: placeholder } = placeholders;
|
|
126
|
+
result += placeholder instanceof RegExp ? placeholder.source : placeholder;
|
|
127
|
+
}
|
|
128
|
+
result += literals[literals.length - 1];
|
|
129
|
+
return new RegExp(result);
|
|
130
|
+
}
|
|
131
|
+
export function defineParser(parser) {
|
|
132
|
+
return parser;
|
|
133
|
+
}
|
package/lib/parser.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LogTimestampFormat, BaseParser } from './models.js';
|
|
2
|
+
import type { AssistedEvent, AttackedEvent, BlindedEvent, ConnectionEvent, EntityTriggeredEvent, KilledByBombEvent, KilledEvent, LeftBuyzoneEvent, PickedUpEvent, PurchasedEvent, RconEvent, SayEvent, ScoredEvent, SuicideEvent, SwitchedTeamEvent, TeamTriggeredEvent, ThrewEvent } from './parsers/index.js';
|
|
3
|
+
export type Event = AssistedEvent | AttackedEvent | BlindedEvent | ConnectionEvent | EntityTriggeredEvent | KilledByBombEvent | KilledEvent | LeftBuyzoneEvent | PickedUpEvent | PurchasedEvent | RconEvent | SayEvent | ScoredEvent | SuicideEvent | SwitchedTeamEvent | TeamTriggeredEvent | ThrewEvent;
|
|
4
|
+
export declare const defaultParsers: (BaseParser<AssistedEvent> | BaseParser<AttackedEvent> | BaseParser<BlindedEvent> | BaseParser<ConnectionEvent> | BaseParser<EntityTriggeredEvent> | BaseParser<KilledByBombEvent> | BaseParser<KilledEvent> | BaseParser<LeftBuyzoneEvent> | BaseParser<PickedUpEvent> | BaseParser<PurchasedEvent> | BaseParser<RconEvent> | BaseParser<SayEvent> | BaseParser<ScoredEvent> | BaseParser<SuicideEvent> | BaseParser<SwitchedTeamEvent> | BaseParser<TeamTriggeredEvent> | BaseParser<ThrewEvent>)[];
|
|
5
|
+
export interface ParseOptions {
|
|
6
|
+
parsers?: BaseParser<Event>[];
|
|
7
|
+
format?: LogTimestampFormat;
|
|
8
|
+
}
|
|
9
|
+
export declare function parse(rawLog: string, parseOptions?: ParseOptions): Event | null;
|
package/lib/parser.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { assistedParser, attackedParser, blindedParser, connectionParser, entityTriggeredParser, killedByBombParser, killedParser, leftBuyzoneParser, pickedUpParser, purchasedParser, rconParser, sayParser, scoredParser, suicideParser, switchedTeamParser, teamTriggeredParser, threwParser } from './parsers/index.js';
|
|
2
|
+
export const defaultParsers = [
|
|
3
|
+
assistedParser,
|
|
4
|
+
attackedParser,
|
|
5
|
+
blindedParser,
|
|
6
|
+
connectionParser,
|
|
7
|
+
entityTriggeredParser,
|
|
8
|
+
killedByBombParser,
|
|
9
|
+
killedParser,
|
|
10
|
+
leftBuyzoneParser,
|
|
11
|
+
pickedUpParser,
|
|
12
|
+
purchasedParser,
|
|
13
|
+
rconParser,
|
|
14
|
+
sayParser,
|
|
15
|
+
scoredParser,
|
|
16
|
+
suicideParser,
|
|
17
|
+
switchedTeamParser,
|
|
18
|
+
teamTriggeredParser,
|
|
19
|
+
threwParser
|
|
20
|
+
];
|
|
21
|
+
// mm/dd/yyyy
|
|
22
|
+
const { length: LENGTH_OF_DATE_FILE } = '01/01/1970 - 00:00:00: ';
|
|
23
|
+
const { length: LENGTH_OF_DATE_HTTP } = '01/01/1970 - 00:00:00.000 - ';
|
|
24
|
+
function parseTimestamp(timestamp) {
|
|
25
|
+
const [dateStr, timeStr] = timestamp.split(' - ');
|
|
26
|
+
const [month, day, year] = dateStr.split('/');
|
|
27
|
+
const [hours, minutes, seconds] = timeStr.split(':');
|
|
28
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
|
|
29
|
+
const dateTimeStringFormat = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`;
|
|
30
|
+
const date = new Date(dateTimeStringFormat);
|
|
31
|
+
return date;
|
|
32
|
+
}
|
|
33
|
+
export function parse(rawLog, parseOptions) {
|
|
34
|
+
if (parseOptions === undefined) {
|
|
35
|
+
parseOptions = {
|
|
36
|
+
parsers: defaultParsers,
|
|
37
|
+
format: 'file'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (parseOptions.parsers === undefined) {
|
|
41
|
+
parseOptions.parsers = defaultParsers;
|
|
42
|
+
}
|
|
43
|
+
if (parseOptions.format === undefined) {
|
|
44
|
+
parseOptions.format = 'file';
|
|
45
|
+
}
|
|
46
|
+
let log;
|
|
47
|
+
let timestamp;
|
|
48
|
+
if (parseOptions.format === 'file') {
|
|
49
|
+
timestamp = rawLog.substring(0, LENGTH_OF_DATE_FILE - 2);
|
|
50
|
+
log = rawLog.substring(LENGTH_OF_DATE_FILE);
|
|
51
|
+
}
|
|
52
|
+
else if (parseOptions.format === 'http') {
|
|
53
|
+
timestamp = rawLog.substring(0, LENGTH_OF_DATE_HTTP - 3);
|
|
54
|
+
log = rawLog.substring(LENGTH_OF_DATE_HTTP);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error('Invalid CS2 log date format specified.');
|
|
58
|
+
}
|
|
59
|
+
for (const parser of parseOptions.parsers) {
|
|
60
|
+
const pattern = parser.patterns.find((pattern) => pattern.test(log));
|
|
61
|
+
if (pattern === undefined) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const groups = log.match(pattern).groups;
|
|
65
|
+
const receivedAt = parseTimestamp(timestamp);
|
|
66
|
+
const payload = parser.parse(groups);
|
|
67
|
+
return {
|
|
68
|
+
name: parser.name,
|
|
69
|
+
receivedAt: receivedAt,
|
|
70
|
+
payload
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export interface AssistedEventPayload {
|
|
3
|
+
assistant: Entity;
|
|
4
|
+
victim: Entity;
|
|
5
|
+
flashAssist: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type AssistedEvent = BaseEvent<'assisted', AssistedEventPayload>;
|
|
8
|
+
export declare const assistedParser: import("../models.js").BaseParser<AssistedEvent>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
;
|
|
3
|
+
export const assistedParser = defineParser({
|
|
4
|
+
name: 'assisted',
|
|
5
|
+
patterns: [
|
|
6
|
+
concatPattern `^(?<assistant>${entityRe}) (?<assistType>assisted|flash-assisted) killing (?<victim>${entityRe})$`
|
|
7
|
+
],
|
|
8
|
+
parse({ assistant, victim, assistType }) {
|
|
9
|
+
return {
|
|
10
|
+
assistant: parseEntity(assistant),
|
|
11
|
+
victim: parseEntity(victim),
|
|
12
|
+
flashAssist: assistType === 'flash-assisted'
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BaseEvent, Entity, HitGroup } from '../models.js';
|
|
2
|
+
export interface AttackedEventPayload {
|
|
3
|
+
attacker: Entity;
|
|
4
|
+
victim: Entity;
|
|
5
|
+
weapon: string;
|
|
6
|
+
damage: number;
|
|
7
|
+
damageArmor: number;
|
|
8
|
+
remainingHealth: number;
|
|
9
|
+
remainingArmor: number;
|
|
10
|
+
hitGroup: HitGroup;
|
|
11
|
+
}
|
|
12
|
+
export type AttackedEvent = BaseEvent<'attacked', AttackedEventPayload>;
|
|
13
|
+
export declare const attackedParser: import("../models.js").BaseParser<AttackedEvent>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, vectorRe, parseEntity, parseVector, parseHitGroup } from '../models.js';
|
|
2
|
+
export const attackedParser = defineParser({
|
|
3
|
+
name: 'attacked',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<attacker>${entityRe}) \\[(?<attackerPos>${vectorRe})\\] attacked (?<victim>${entityRe}) \\[(?<victimPos>${vectorRe})\\] with "(?<weapon>[^"]+)" \\(damage "(?<damageAmount>[^"]+)"\\) \\(damage_armor "(?<damageArmorAmount>[^"]+)"\\) \\(health "(?<remainingHealth>[^"]+)"\\) \\(armor "(?<remainingArmor>[^"]+)"\\) \\(hitgroup "(?<hitGroup>[^"]+)"\\)$`,
|
|
6
|
+
],
|
|
7
|
+
parse: ({ attacker, attackerPos, victim, victimPos, weapon, damageAmount, damageArmorAmount, remainingHealth, remainingArmor, hitGroup }) => {
|
|
8
|
+
return {
|
|
9
|
+
attacker: {
|
|
10
|
+
...parseEntity(attacker),
|
|
11
|
+
position: parseVector(attackerPos)
|
|
12
|
+
},
|
|
13
|
+
victim: {
|
|
14
|
+
...parseEntity(victim),
|
|
15
|
+
position: parseVector(victimPos)
|
|
16
|
+
},
|
|
17
|
+
damage: Number(damageAmount),
|
|
18
|
+
damageArmor: Number(damageArmorAmount),
|
|
19
|
+
remainingHealth: Number(remainingHealth),
|
|
20
|
+
remainingArmor: Number(remainingArmor),
|
|
21
|
+
hitGroup: parseHitGroup(hitGroup),
|
|
22
|
+
weapon
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export interface BlindedEventPayload {
|
|
3
|
+
attacker: Entity;
|
|
4
|
+
victim: Entity;
|
|
5
|
+
blindDuration: number;
|
|
6
|
+
flashbangIndex: number;
|
|
7
|
+
}
|
|
8
|
+
export type BlindedEvent = BaseEvent<'blinded', BlindedEventPayload>;
|
|
9
|
+
export declare const blindedParser: import("../models.js").BaseParser<BlindedEvent>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { concatPattern, defineParser, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
export const blindedParser = defineParser({
|
|
3
|
+
name: 'blinded',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<victim>${entityRe}) blinded for (?<blindDuration>[^"]+) by (?<attacker>${entityRe}) from flashbang entindex (?<entIndex>[^"]+)$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ attacker, victim, blindDuration, entIndex }) => {
|
|
8
|
+
return {
|
|
9
|
+
attacker: parseEntity(attacker),
|
|
10
|
+
victim: parseEntity(victim),
|
|
11
|
+
blindDuration: Number(blindDuration),
|
|
12
|
+
flashbangIndex: Number(entIndex)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ConnectionState, BaseEvent, Entity } from '../models.js';
|
|
2
|
+
interface BaseConnectionEvent {
|
|
3
|
+
state: ConnectionState;
|
|
4
|
+
client: Entity;
|
|
5
|
+
}
|
|
6
|
+
interface ConnectedEvent extends BaseConnectionEvent {
|
|
7
|
+
state: 'connected';
|
|
8
|
+
address: string;
|
|
9
|
+
}
|
|
10
|
+
interface EnteredEvent extends BaseConnectionEvent {
|
|
11
|
+
state: 'entered';
|
|
12
|
+
}
|
|
13
|
+
interface DisconnectedEvent extends BaseConnectionEvent {
|
|
14
|
+
state: 'disconnected';
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
export type ConnectionEventPayload = ConnectedEvent | EnteredEvent | DisconnectedEvent;
|
|
18
|
+
export type ConnectionEvent = BaseEvent<'connection', ConnectionEventPayload>;
|
|
19
|
+
export declare const connectionParser: import("../models.js").BaseParser<ConnectionEvent>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity, parseConnectionState } from '../models.js';
|
|
2
|
+
const basePattern = concatPattern `^(?<client>${entityRe})`;
|
|
3
|
+
export const connectionParser = defineParser({
|
|
4
|
+
name: 'connection',
|
|
5
|
+
patterns: [
|
|
6
|
+
concatPattern `${basePattern} (?<state>entered) the game$`,
|
|
7
|
+
concatPattern `${basePattern} (?<state>connected), address "(?<address>[^"]*)"$`,
|
|
8
|
+
concatPattern `${basePattern} (?<state>disconnected) \\(reason "(?<reason>[^"]*)"\\)$`,
|
|
9
|
+
],
|
|
10
|
+
parse: ({ client: rawClient, state: rawState, address, reason }) => {
|
|
11
|
+
const state = parseConnectionState(rawState);
|
|
12
|
+
const client = parseEntity(rawClient);
|
|
13
|
+
if (state === 'connected') {
|
|
14
|
+
return {
|
|
15
|
+
state,
|
|
16
|
+
client,
|
|
17
|
+
address
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else if (state === 'entered') {
|
|
21
|
+
return {
|
|
22
|
+
state,
|
|
23
|
+
client
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return {
|
|
28
|
+
state,
|
|
29
|
+
client,
|
|
30
|
+
reason
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Entity, BaseEvent } from '../models.js';
|
|
2
|
+
type EntityTriggeredSubEvent = 'game_commencing' | 'warmup_start' | 'warmup_end' | 'match_start' | 'round_start' | 'round_end' | 'begin_bomb_defuse_with_kit' | 'begin_bomb_defuse_without_kit' | 'defused_the_bomb' | 'planted_the_bomb' | 'got_the_bomb' | 'dropped_the_bomb' | 'touched_a_hostage' | 'rescued_a_hostage';
|
|
3
|
+
export interface EntityTriggeredEventPayload {
|
|
4
|
+
entity: Entity;
|
|
5
|
+
event: EntityTriggeredSubEvent;
|
|
6
|
+
value: string | null;
|
|
7
|
+
}
|
|
8
|
+
export type EntityTriggeredEvent = BaseEvent<'entity_triggered', EntityTriggeredEventPayload>;
|
|
9
|
+
export declare const entityTriggeredParser: import("../models.js").BaseParser<EntityTriggeredEvent>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
export const entityTriggeredParser = defineParser({
|
|
3
|
+
name: 'entity_triggered',
|
|
4
|
+
patterns: [
|
|
5
|
+
// concatPattern`^(?<entity>${entityRe}) triggered "(?<event>[^"]+)"(?: \\(value "(?<value>[^"]+)"\\))?$`,
|
|
6
|
+
concatPattern `^(?<entity>${entityRe}) triggered "(?<event>[^"]+)"$`,
|
|
7
|
+
concatPattern `^(?<entity>${entityRe}) triggered "(?<event>[^"]+)" on "(?<value>[^"]+)"$`,
|
|
8
|
+
concatPattern `^(?<entity>${entityRe}) triggered "(?<event>[^"]+)" at bombsite (?<value>[^"]+)$`
|
|
9
|
+
],
|
|
10
|
+
parse: ({ entity, event, value }) => {
|
|
11
|
+
return {
|
|
12
|
+
entity: parseEntity(entity),
|
|
13
|
+
event: event.toLowerCase(),
|
|
14
|
+
value: value ?? null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './assisted.js';
|
|
2
|
+
export * from './attacked.js';
|
|
3
|
+
export * from './blinded.js';
|
|
4
|
+
export * from './connection.js';
|
|
5
|
+
export * from './entity-triggered.js';
|
|
6
|
+
export * from './killed-by-bomb.js';
|
|
7
|
+
export * from './killed.js';
|
|
8
|
+
export * from './left-buyzone.js';
|
|
9
|
+
export * from './picked-up.js';
|
|
10
|
+
export * from './purchased.js';
|
|
11
|
+
export * from './rcon.js';
|
|
12
|
+
export * from './say.js';
|
|
13
|
+
export * from './scored.js';
|
|
14
|
+
export * from './suicide.js';
|
|
15
|
+
export * from './switched-team.js';
|
|
16
|
+
export * from './team-triggered.js';
|
|
17
|
+
export * from './threw.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './assisted.js';
|
|
2
|
+
export * from './attacked.js';
|
|
3
|
+
export * from './blinded.js';
|
|
4
|
+
export * from './connection.js';
|
|
5
|
+
export * from './entity-triggered.js';
|
|
6
|
+
export * from './killed-by-bomb.js';
|
|
7
|
+
export * from './killed.js';
|
|
8
|
+
export * from './left-buyzone.js';
|
|
9
|
+
export * from './picked-up.js';
|
|
10
|
+
export * from './purchased.js';
|
|
11
|
+
export * from './rcon.js';
|
|
12
|
+
export * from './say.js';
|
|
13
|
+
export * from './scored.js';
|
|
14
|
+
export * from './suicide.js';
|
|
15
|
+
export * from './switched-team.js';
|
|
16
|
+
export * from './team-triggered.js';
|
|
17
|
+
export * from './threw.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export type KilledByBombEventPayload = Entity;
|
|
3
|
+
export type KilledByBombEvent = BaseEvent<'killed_by_bomb', KilledByBombEventPayload>;
|
|
4
|
+
export declare const killedByBombParser: import("../models.js").BaseParser<KilledByBombEvent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, vectorRe, parseEntity, parseVector } from '../models.js';
|
|
2
|
+
export const killedByBombParser = defineParser({
|
|
3
|
+
name: 'killed_by_bomb',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<victim>${entityRe}) \\[(?<victimPos>${vectorRe})\\] was killed by the bomb.$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ victim, victimPos }) => {
|
|
8
|
+
return {
|
|
9
|
+
...parseEntity(victim),
|
|
10
|
+
position: parseVector(victimPos)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseEvent, Entity, KillModifier } from '../models.js';
|
|
2
|
+
export interface KilledEventPayload {
|
|
3
|
+
attacker: Entity;
|
|
4
|
+
victim: Entity;
|
|
5
|
+
weapon: string;
|
|
6
|
+
modifiers: KillModifier[];
|
|
7
|
+
}
|
|
8
|
+
export type KilledEvent = BaseEvent<'killed', KilledEventPayload>;
|
|
9
|
+
export declare const killedParser: import("../models.js").BaseParser<KilledEvent>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, vectorRe, parseEntity, parseVector, parseKillModifiers, baseEntityRe } from '../models.js';
|
|
2
|
+
export const killedParser = defineParser({
|
|
3
|
+
name: 'killed',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<attacker>${entityRe}) \\[(?<attackerPos>${vectorRe})\\] killed (?<victim>${entityRe}) \\[(?<victimPos>${vectorRe})\\] with "(?<weapon>[^"]+)"(?: \\((?<modifiers>[^\\)]+)\\))?$`,
|
|
6
|
+
concatPattern `^(?<attacker>${entityRe}) \\[(?<attackerPos>${vectorRe})\\] killed other (?<victim>${baseEntityRe}) \\[(?<victimPos>${vectorRe})\\] with "(?<weapon>[^"]+)"(?: \\((?<modifiers>[^\\)]+)\\))?$`,
|
|
7
|
+
],
|
|
8
|
+
parse: ({ attacker, attackerPos, victim, victimPos, weapon, modifiers }) => {
|
|
9
|
+
return {
|
|
10
|
+
attacker: {
|
|
11
|
+
...parseEntity(attacker),
|
|
12
|
+
position: parseVector(attackerPos)
|
|
13
|
+
},
|
|
14
|
+
victim: {
|
|
15
|
+
...parseEntity(victim),
|
|
16
|
+
position: parseVector(victimPos)
|
|
17
|
+
},
|
|
18
|
+
modifiers: parseKillModifiers(modifiers),
|
|
19
|
+
weapon
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export interface LeftBuyzoneEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
equipment: string[];
|
|
5
|
+
}
|
|
6
|
+
export type LeftBuyzoneEvent = BaseEvent<'left_buyzone', LeftBuyzoneEventPayload>;
|
|
7
|
+
export declare const leftBuyzoneParser: import("../models.js").BaseParser<LeftBuyzoneEvent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { concatPattern, defineParser, entityRe, parseEntity, parseEquipmentList } from '../models.js';
|
|
2
|
+
export const leftBuyzoneParser = defineParser({
|
|
3
|
+
name: 'left_buyzone',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) left buyzone with (?<equipment>\\[.*\\])$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, equipment }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: parseEntity(player),
|
|
10
|
+
equipment: parseEquipmentList(equipment)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export interface PickedUpEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
weapon: string;
|
|
5
|
+
}
|
|
6
|
+
export type PickedUpEvent = BaseEvent<'picked_up', PickedUpEventPayload>;
|
|
7
|
+
export declare const pickedUpParser: import("../models.js").BaseParser<PickedUpEvent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
export const pickedUpParser = defineParser({
|
|
3
|
+
name: 'picked_up',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) picked up "(?<weapon>[^"]+)"$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, weapon }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: parseEntity(player),
|
|
10
|
+
weapon
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BaseEvent, Entity } from '../models.js';
|
|
2
|
+
export interface PurchasedEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
weapon: string;
|
|
5
|
+
}
|
|
6
|
+
export type PurchasedEvent = BaseEvent<'purchased', PurchasedEventPayload>;
|
|
7
|
+
export declare const purchasedParser: import("../models.js").BaseParser<PurchasedEvent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
export const purchasedParser = defineParser({
|
|
3
|
+
name: 'purchased',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) purchased "(?<weapon>[^"]+)"$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, weapon }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: parseEntity(player),
|
|
10
|
+
weapon
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BaseEvent } from '../models.js';
|
|
2
|
+
export interface RconEventPayload {
|
|
3
|
+
address: string;
|
|
4
|
+
command: string;
|
|
5
|
+
}
|
|
6
|
+
export type RconEvent = BaseEvent<'rcon', RconEventPayload>;
|
|
7
|
+
export declare const rconParser: import("../models.js").BaseParser<RconEvent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineParser, concatPattern } from '../models.js';
|
|
2
|
+
export const rconParser = defineParser({
|
|
3
|
+
name: 'rcon',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^rcon from "(?<address>.+)": command "(?<command>.+)"$`,
|
|
6
|
+
],
|
|
7
|
+
parse: ({ address, command }) => {
|
|
8
|
+
return {
|
|
9
|
+
address,
|
|
10
|
+
command
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BaseEvent, Entity, SayTo } from '../models.js';
|
|
2
|
+
export interface SayEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
to: SayTo;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export type SayEvent = BaseEvent<'say', SayEventPayload>;
|
|
8
|
+
export declare const sayParser: import("../models.js").BaseParser<SayEvent>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity } from '../models.js';
|
|
2
|
+
export const sayParser = defineParser({
|
|
3
|
+
name: 'say',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) (?<to>say|say_team) "(?<message>.+)"$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, to, message }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: parseEntity(player),
|
|
10
|
+
to: to === 'say_team' ? 'team' : 'all',
|
|
11
|
+
message
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BaseEvent, Team } from '../models.js';
|
|
2
|
+
export interface ScoredEventPayload {
|
|
3
|
+
team: Team;
|
|
4
|
+
score: number;
|
|
5
|
+
playerCount: number;
|
|
6
|
+
}
|
|
7
|
+
export type ScoredEvent = BaseEvent<'scored', ScoredEventPayload>;
|
|
8
|
+
export declare const scoredParser: import("../models.js").BaseParser<ScoredEvent>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineParser, parseTeam, concatPattern } from '../models.js';
|
|
2
|
+
export const scoredParser = defineParser({
|
|
3
|
+
name: 'scored',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^Team "(?<team>[^"]+)" scored "(?<score>[^"]+)" with "(?<playerCount>[^"]+)" players$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ team, score, playerCount }) => {
|
|
8
|
+
return {
|
|
9
|
+
team: parseTeam(team),
|
|
10
|
+
score: Number(score),
|
|
11
|
+
playerCount: Number(playerCount)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Entity, SuicideMethod, BaseEvent } from '../models.js';
|
|
2
|
+
export interface SuicideEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
method: SuicideMethod;
|
|
5
|
+
}
|
|
6
|
+
export type SuicideEvent = BaseEvent<'suicide', SuicideEventPayload>;
|
|
7
|
+
export declare const suicideParser: import("../models.js").BaseParser<SuicideEvent>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, vectorRe, parseEntity, parseVector } from '../models.js';
|
|
2
|
+
export const suicideParser = defineParser({
|
|
3
|
+
name: 'suicide',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) \\[(?<playerPos>${vectorRe})\\] committed suicide with "(?<how>.+)"$`,
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, playerPos, how }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: {
|
|
10
|
+
...parseEntity(player),
|
|
11
|
+
position: parseVector(playerPos),
|
|
12
|
+
},
|
|
13
|
+
method: how
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Entity, Team, BaseEvent } from '../models.js';
|
|
2
|
+
export interface SwitchedTeamEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
fromTeam: Team;
|
|
5
|
+
toTeam: Team;
|
|
6
|
+
}
|
|
7
|
+
export type SwitchedTeamEvent = BaseEvent<'switched_team', SwitchedTeamEventPayload>;
|
|
8
|
+
export declare const switchedTeamParser: import("../models.js").BaseParser<SwitchedTeamEvent>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineParser, concatPattern, entityRe, parseEntity, parseTeam } from '../models.js';
|
|
2
|
+
export const switchedTeamParser = defineParser({
|
|
3
|
+
name: 'switched_team',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) switched from team <(?<fromTeam>[^>]+)> to <(?<toTeam>[^>]+)>$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ player, fromTeam, toTeam }) => {
|
|
8
|
+
return {
|
|
9
|
+
player: parseEntity(player),
|
|
10
|
+
fromTeam: parseTeam(fromTeam),
|
|
11
|
+
toTeam: parseTeam(toTeam)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Team, BaseEvent } from '../models.js';
|
|
2
|
+
type TeamTriggeredSubEvent = 'sfui_notice_terrorists_win' | 'sfui_notice_cts_win' | 'sfui_notice_target_bombed' | 'sfui_notice_target_saved' | 'sfui_notice_bomb_defused' | 'sfui_notice_hostages_not_rescued' | 'sfui_notice_all_hostages_rescued';
|
|
3
|
+
export interface TeamTriggeredEventPayload {
|
|
4
|
+
team: Team;
|
|
5
|
+
event: TeamTriggeredSubEvent;
|
|
6
|
+
tScore: number;
|
|
7
|
+
ctScore: number;
|
|
8
|
+
}
|
|
9
|
+
export type TeamTriggeredEvent = BaseEvent<'team_triggered', TeamTriggeredEventPayload>;
|
|
10
|
+
export declare const teamTriggeredParser: import("../models.js").BaseParser<TeamTriggeredEvent>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseTeam, defineParser, concatPattern } from '../models.js';
|
|
2
|
+
export const teamTriggeredParser = defineParser({
|
|
3
|
+
name: 'team_triggered',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^Team "(?<team>[^"]+)" triggered "(?<event>[^"]+)" \\(CT "(?<ctScore>\\d+)"\\) \\(T "(?<tScore>\\d+)"\\)$`
|
|
6
|
+
],
|
|
7
|
+
parse: ({ team, tScore, ctScore, event }) => {
|
|
8
|
+
return {
|
|
9
|
+
team: parseTeam(team),
|
|
10
|
+
tScore: Number(tScore),
|
|
11
|
+
ctScore: Number(ctScore),
|
|
12
|
+
event: event.toLowerCase()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Entity, BaseEvent, Vector } from '../models.js';
|
|
2
|
+
export interface ThrewEventPayload {
|
|
3
|
+
player: Entity;
|
|
4
|
+
grenade: string;
|
|
5
|
+
position: Vector;
|
|
6
|
+
entIndex: number | null;
|
|
7
|
+
}
|
|
8
|
+
export type ThrewEvent = BaseEvent<'threw', ThrewEventPayload>;
|
|
9
|
+
export declare const threwParser: import("../models.js").BaseParser<ThrewEvent>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { concatPattern, defineParser, entityRe, vectorRe, parseEntity, parseVector } from '../models.js';
|
|
2
|
+
export const threwParser = defineParser({
|
|
3
|
+
name: 'threw',
|
|
4
|
+
patterns: [
|
|
5
|
+
concatPattern `^(?<player>${entityRe}) threw (?<grenade>.+) \\[(?<position>${vectorRe})\\]$`,
|
|
6
|
+
concatPattern `^(?<player>${entityRe}) threw (?<grenade>.+) \\[(?<position>${vectorRe})\\] flashbang entindex (?<entIndex>.+)\\)$`
|
|
7
|
+
],
|
|
8
|
+
parse: ({ player, grenade, position, entIndex }) => {
|
|
9
|
+
return {
|
|
10
|
+
player: parseEntity(player),
|
|
11
|
+
position: parseVector(position),
|
|
12
|
+
entIndex: Number(entIndex) || null,
|
|
13
|
+
grenade
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cs2-log-parser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Parses CS2 logs into JavaScript objects.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "GekasD",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"cs2",
|
|
9
|
+
"source2",
|
|
10
|
+
"log",
|
|
11
|
+
"parser",
|
|
12
|
+
"counter-strike",
|
|
13
|
+
"counter-strike-2"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/GekasD/cs2-log-parser",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"files": [
|
|
19
|
+
"lib/**/*"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "npm run clean && npm run lint && npm run transpile",
|
|
26
|
+
"clean": "rimraf lib",
|
|
27
|
+
"lint": "eslint --fix",
|
|
28
|
+
"transpile": "tsc",
|
|
29
|
+
"test": "node --import tsx --test tests/*.test.ts",
|
|
30
|
+
"prepare": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
34
|
+
"@eslint/js": "^9.37.0",
|
|
35
|
+
"@types/node": "^24.6.2",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.46.0",
|
|
38
|
+
"eslint": "^9.37.0",
|
|
39
|
+
"rimraf": "^6.0.1",
|
|
40
|
+
"tsx": "^4.20.6",
|
|
41
|
+
"typescript": "^5.9.2"
|
|
42
|
+
}
|
|
43
|
+
}
|