truckcord-client 1.0.3

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.
Files changed (53) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +58 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/config.d.ts +24 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +17 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/constants.d.ts +8 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/constants.js +8 -0
  12. package/dist/constants.js.map +1 -0
  13. package/dist/detectors/index.d.ts +20 -0
  14. package/dist/detectors/index.d.ts.map +1 -0
  15. package/dist/detectors/index.js +76 -0
  16. package/dist/detectors/index.js.map +1 -0
  17. package/dist/discord.d.ts +13 -0
  18. package/dist/discord.d.ts.map +1 -0
  19. package/dist/discord.js +102 -0
  20. package/dist/discord.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +203 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/logger.d.ts +3 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +13 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/richpresence/index.d.ts +9 -0
  30. package/dist/richpresence/index.d.ts.map +1 -0
  31. package/dist/richpresence/index.js +46 -0
  32. package/dist/richpresence/index.js.map +1 -0
  33. package/dist/telemetry/index.d.ts +13 -0
  34. package/dist/telemetry/index.d.ts.map +1 -0
  35. package/dist/telemetry/index.js +34 -0
  36. package/dist/telemetry/index.js.map +1 -0
  37. package/dist/tracker/index.d.ts +14 -0
  38. package/dist/tracker/index.d.ts.map +1 -0
  39. package/dist/tracker/index.js +50 -0
  40. package/dist/tracker/index.js.map +1 -0
  41. package/dist/tracker/index.test.d.ts +2 -0
  42. package/dist/tracker/index.test.d.ts.map +1 -0
  43. package/dist/tracker/index.test.js +54 -0
  44. package/dist/tracker/index.test.js.map +1 -0
  45. package/dist/utils.d.ts +11 -0
  46. package/dist/utils.d.ts.map +1 -0
  47. package/dist/utils.js +10 -0
  48. package/dist/utils.js.map +1 -0
  49. package/dist/ws/index.d.ts +20 -0
  50. package/dist/ws/index.d.ts.map +1 -0
  51. package/dist/ws/index.js +88 -0
  52. package/dist/ws/index.js.map +1 -0
  53. package/package.json +37 -0
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { stdin, stdout, exit } from 'node:process';
4
+ import { createDiscordConnection } from './discord.js';
5
+ import { validateConfig } from './config.js';
6
+ import { startClient } from './index.js';
7
+ import { logger } from './logger.js';
8
+ async function prompt(question, defaultValue) {
9
+ const rl = createInterface({ input: stdin, output: stdout });
10
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
11
+ const answer = await rl.question(`${question}${suffix}: `);
12
+ rl.close();
13
+ return answer.trim() || defaultValue || '';
14
+ }
15
+ function getDefaultUsername() {
16
+ return process.env.USER || process.env.USERNAME || 'Unknown Driver';
17
+ }
18
+ function printHeader() {
19
+ console.log('');
20
+ console.log(' TruckCord Client v2.0.0');
21
+ console.log(' ETS2/ATS telemetry Discord integration');
22
+ console.log('');
23
+ }
24
+ async function main() {
25
+ printHeader();
26
+ const wsUrl = await prompt('WebSocket server URL', 'ws://localhost:3001');
27
+ const discordAppId = await prompt('Discord Application ID');
28
+ const discordUsername = await prompt('Your display name', getDefaultUsername());
29
+ if (!discordAppId) {
30
+ console.error('Discord Application ID is required.');
31
+ exit(1);
32
+ }
33
+ console.log('');
34
+ console.log(' Connecting to Discord RPC...');
35
+ let discord;
36
+ try {
37
+ discord = await createDiscordConnection(discordAppId);
38
+ }
39
+ catch (err) {
40
+ console.error(` ${err instanceof Error ? err.message : String(err)}`);
41
+ exit(1);
42
+ }
43
+ console.log(` Discord connected - User ID: ${discord.userId}`);
44
+ console.log('');
45
+ const config = validateConfig({
46
+ wsUrl,
47
+ discordUserId: discord.userId,
48
+ discordAppId,
49
+ discordUsername,
50
+ logLevel: 'info',
51
+ });
52
+ await startClient(config, discord);
53
+ }
54
+ main().catch((err) => {
55
+ logger.error({ err }, 'Client error');
56
+ exit(1);
57
+ });
58
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,cAAc,EAAqB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,KAAK,UAAU,MAAM,CAAC,QAAgB,EAAE,YAAqB;IAC3D,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,MAAM,IAAI,CAAC,CAAC;IAC3D,EAAE,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,MAAM,CAAC,IAAI,EAAE,IAAI,YAAY,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,gBAAgB,CAAC;AACtE,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,WAAW,EAAE,CAAC;IAEd,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,EAAE,qBAAqB,CAAC,CAAC;IAC1E,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC5D,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC,mBAAmB,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAEhF,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAE9C,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,kCAAkC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAiB,cAAc,CAAC;QAC1C,KAAK;QACL,aAAa,EAAE,OAAO,CAAC,MAAM;QAC7B,YAAY;QACZ,eAAe;QACf,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;IACtC,IAAI,CAAC,CAAC,CAAC,CAAC;AACV,CAAC,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ declare const configSchema: z.ZodObject<{
3
+ wsUrl: z.ZodDefault<z.ZodString>;
4
+ discordUserId: z.ZodString;
5
+ discordAppId: z.ZodString;
6
+ discordUsername: z.ZodDefault<z.ZodString>;
7
+ logLevel: z.ZodDefault<z.ZodEnum<["trace", "debug", "info", "warn", "error", "fatal"]>>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ wsUrl: string;
10
+ discordUserId: string;
11
+ discordAppId: string;
12
+ discordUsername: string;
13
+ logLevel: "fatal" | "error" | "warn" | "info" | "debug" | "trace";
14
+ }, {
15
+ discordUserId: string;
16
+ discordAppId: string;
17
+ wsUrl?: string | undefined;
18
+ discordUsername?: string | undefined;
19
+ logLevel?: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | undefined;
20
+ }>;
21
+ export type ClientConfig = z.infer<typeof configSchema>;
22
+ export declare function validateConfig(raw: Partial<ClientConfig>): ClientConfig;
23
+ export {};
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;EAMhB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAOvE"}
package/dist/config.js ADDED
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ const configSchema = z.object({
3
+ wsUrl: z.string().url().default('ws://localhost:3001'),
4
+ discordUserId: z.string().min(1, 'Discord user ID is required'),
5
+ discordAppId: z.string().min(1, 'Discord application ID is required'),
6
+ discordUsername: z.string().default('Unknown Driver'),
7
+ logLevel: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),
8
+ });
9
+ export function validateConfig(raw) {
10
+ const parsed = configSchema.safeParse(raw);
11
+ if (!parsed.success) {
12
+ console.error('Invalid config:', parsed.error.flatten().fieldErrors);
13
+ process.exit(1);
14
+ }
15
+ return parsed.data;
16
+ }
17
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC;IACtD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,6BAA6B,CAAC;IAC/D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,oCAAoC,CAAC;IACrE,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC;IACrD,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;CACvF,CAAC,CAAC;AAIH,MAAM,UAAU,cAAc,CAAC,GAA0B;IACvD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const DAMAGE_SPIKE_THRESHOLD = 5;
2
+ export declare const DAMAGE_SPIKE_WINDOW_MS = 2000;
3
+ export declare const FUEL_LOW_THRESHOLD = 10;
4
+ export declare const TELEMETRY_INTERVAL_MS = 500;
5
+ export declare const RICH_PRESENCE_UPDATE_MS = 5000;
6
+ export declare const WS_RECONNECT_INTERVAL_MS = 5000;
7
+ export declare const WS_RECONNECT_MAX_ATTEMPTS = 10;
8
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,yBAAyB,KAAK,CAAC"}
@@ -0,0 +1,8 @@
1
+ export const DAMAGE_SPIKE_THRESHOLD = 5;
2
+ export const DAMAGE_SPIKE_WINDOW_MS = 2000;
3
+ export const FUEL_LOW_THRESHOLD = 10;
4
+ export const TELEMETRY_INTERVAL_MS = 500;
5
+ export const RICH_PRESENCE_UPDATE_MS = 5000;
6
+ export const WS_RECONNECT_INTERVAL_MS = 5000;
7
+ export const WS_RECONNECT_MAX_ATTEMPTS = 10;
8
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAC5C,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { SCSSDKTelemetry } from 'trucksim-telemetry';
2
+ interface DetectorState {
3
+ lastDamageCheck: {
4
+ time: number;
5
+ totalDamage: number;
6
+ } | null;
7
+ lastFuelWarning: boolean;
8
+ lastFineTime: number;
9
+ }
10
+ export declare function createDetectorState(): DetectorState;
11
+ export interface DetectorEvent {
12
+ type: 'accident' | 'low_fuel' | 'fine' | 'speeding';
13
+ severity: 'low' | 'medium' | 'high' | 'critical';
14
+ details: Record<string, unknown>;
15
+ }
16
+ export declare function detectDamageSpike(data: SCSSDKTelemetry, state: DetectorState): DetectorEvent | null;
17
+ export declare function detectLowFuel(data: SCSSDKTelemetry, state: DetectorState): DetectorEvent | null;
18
+ export declare function detectSpeeding(data: SCSSDKTelemetry, _speedLimit: number | null): DetectorEvent | null;
19
+ export {};
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/detectors/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D,UAAU,aAAa;IACrB,eAAe,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,mBAAmB,IAAI,aAAa,CAMnD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACpD,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI,CA8BnG;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI,CAuB/F;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,GAAG,IAAI,CAsBtG"}
@@ -0,0 +1,76 @@
1
+ import { DAMAGE_SPIKE_THRESHOLD, FUEL_LOW_THRESHOLD } from '../constants.js';
2
+ import { logger } from '../logger.js';
3
+ export function createDetectorState() {
4
+ return {
5
+ lastDamageCheck: null,
6
+ lastFuelWarning: false,
7
+ lastFineTime: 0,
8
+ };
9
+ }
10
+ export function detectDamageSpike(data, state) {
11
+ const totalDamage = data.wearEngine + data.wearTransmission + data.wearCabin + data.wearChassis + data.wearWheels;
12
+ const now = Date.now();
13
+ if (state.lastDamageCheck && state.lastDamageCheck.time > 0) {
14
+ const damageDiff = (totalDamage - state.lastDamageCheck.totalDamage) * 100;
15
+ const timeDiff = now - state.lastDamageCheck.time;
16
+ if (timeDiff <= 2000 && damageDiff >= DAMAGE_SPIKE_THRESHOLD) {
17
+ logger.warn({ damageDiff, timeDiff }, 'Damage spike detected');
18
+ state.lastDamageCheck = { time: 0, totalDamage: 0 };
19
+ const severity = damageDiff >= 20 ? 'critical' : damageDiff >= 10 ? 'high' : 'medium';
20
+ return {
21
+ type: 'accident',
22
+ severity,
23
+ details: {
24
+ damagePercent: Math.round(damageDiff * 100) / 100,
25
+ speedKmh: Math.round(data.speed * 3.6 * 10) / 10,
26
+ engineDamage: data.wearEngine * 100,
27
+ transmissionDamage: data.wearTransmission * 100,
28
+ },
29
+ };
30
+ }
31
+ }
32
+ state.lastDamageCheck = { time: now, totalDamage };
33
+ return null;
34
+ }
35
+ export function detectLowFuel(data, state) {
36
+ if (data.fuelCapacity <= 0)
37
+ return null;
38
+ const fuelPercent = Math.round((data.fuel / data.fuelCapacity) * 100);
39
+ if (fuelPercent <= FUEL_LOW_THRESHOLD && !state.lastFuelWarning) {
40
+ state.lastFuelWarning = true;
41
+ return {
42
+ type: 'low_fuel',
43
+ severity: fuelPercent <= 3 ? 'critical' : 'medium',
44
+ details: {
45
+ fuelPercent,
46
+ fuelLiters: Math.round(data.fuel * 10) / 10,
47
+ fuelRange: Math.round(data.fuelRange),
48
+ },
49
+ };
50
+ }
51
+ if (fuelPercent > FUEL_LOW_THRESHOLD) {
52
+ state.lastFuelWarning = false;
53
+ }
54
+ return null;
55
+ }
56
+ export function detectSpeeding(data, _speedLimit) {
57
+ if (!data.speedLimit || data.speedLimit <= 0)
58
+ return null;
59
+ const speedKmh = data.speed * 3.6;
60
+ const limitKmh = data.speedLimit * 3.6;
61
+ if (speedKmh > limitKmh * 1.2 && speedKmh > 30) {
62
+ const overPercent = Math.round(((speedKmh - limitKmh) / limitKmh) * 100);
63
+ const severity = overPercent > 50 ? 'high' : overPercent > 30 ? 'medium' : 'low';
64
+ return {
65
+ type: 'speeding',
66
+ severity,
67
+ details: {
68
+ speedKmh: Math.round(speedKmh),
69
+ limitKmh: Math.round(limitKmh),
70
+ overPercent,
71
+ },
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/detectors/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAQtC,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,eAAe,EAAE,IAAI;QACrB,eAAe,EAAE,KAAK;QACtB,YAAY,EAAE,CAAC;KAChB,CAAC;AACJ,CAAC;AAQD,MAAM,UAAU,iBAAiB,CAAC,IAAqB,EAAE,KAAoB;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC;IAClH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,IAAI,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,UAAU,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC;QAC3E,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC;QAElD,IAAI,QAAQ,IAAI,IAAI,IAAI,UAAU,IAAI,sBAAsB,EAAE,CAAC;YAC7D,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAE/D,KAAK,CAAC,eAAe,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;YAEpD,MAAM,QAAQ,GAAG,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;YAEtF,OAAO;gBACL,IAAI,EAAE,UAAU;gBAChB,QAAQ;gBACR,OAAO,EAAE;oBACP,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,GAAG;oBACjD,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE;oBAChD,YAAY,EAAE,IAAI,CAAC,UAAU,GAAG,GAAG;oBACnC,kBAAkB,EAAE,IAAI,CAAC,gBAAgB,GAAG,GAAG;iBAChD;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;IACnD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAqB,EAAE,KAAoB;IACvE,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC;IAEtE,IAAI,WAAW,IAAI,kBAAkB,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;QAChE,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ;YAClD,OAAO,EAAE;gBACP,WAAW;gBACX,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,EAAE;gBAC3C,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;aACtC;SACF,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,GAAG,kBAAkB,EAAE,CAAC;QACrC,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAqB,EAAE,WAA0B;IAC9E,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IAEvC,IAAI,QAAQ,GAAG,QAAQ,GAAG,GAAG,IAAI,QAAQ,GAAG,EAAE,EAAE,CAAC;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QAEjF,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,QAAQ;YACR,OAAO,EAAE;gBACP,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC9B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC9B,WAAW;aACZ;SACF,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Client } from 'discord-rpc';
2
+ type ConnectionHandler = () => void;
3
+ export interface DiscordConnection {
4
+ readonly userId: string;
5
+ readonly isConnected: boolean;
6
+ readonly rpcClient: Client | null;
7
+ onConnected(handler: ConnectionHandler): void;
8
+ onDisconnected(handler: ConnectionHandler): void;
9
+ destroy(): Promise<void>;
10
+ }
11
+ export declare function createDiscordConnection(appId: string): Promise<DiscordConnection>;
12
+ export {};
13
+ //# sourceMappingURL=discord.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord.d.ts","sourceRoot":"","sources":["../src/discord.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,KAAK,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC9C,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoGvF"}
@@ -0,0 +1,102 @@
1
+ import { Client } from 'discord-rpc';
2
+ import { logger } from './logger.js';
3
+ export async function createDiscordConnection(appId) {
4
+ let client = null;
5
+ let _userId = '';
6
+ let _isConnected = false;
7
+ const connectedHandlers = [];
8
+ const disconnectedHandlers = [];
9
+ let reconnectTimer = null;
10
+ let destroyed = false;
11
+ async function createClient() {
12
+ try {
13
+ client = new Client({ transport: 'ipc' });
14
+ await new Promise((resolve, reject) => {
15
+ const timeout = setTimeout(() => reject(new Error('Discord RPC connection timeout')), 8000);
16
+ client.on('ready', () => {
17
+ clearTimeout(timeout);
18
+ _userId = client.user?.id ?? '';
19
+ if (!_userId) {
20
+ reject(new Error('Could not get Discord user ID'));
21
+ return;
22
+ }
23
+ _isConnected = true;
24
+ logger.info({ userId: _userId }, 'Discord RPC connected');
25
+ connectedHandlers.forEach((h) => h());
26
+ resolve();
27
+ });
28
+ client.on('disconnected', () => {
29
+ _isConnected = false;
30
+ logger.warn('Discord RPC disconnected');
31
+ disconnectedHandlers.forEach((h) => h());
32
+ scheduleReconnect();
33
+ });
34
+ client.login({ clientId: appId }).catch(reject);
35
+ });
36
+ return true;
37
+ }
38
+ catch (err) {
39
+ if (client) {
40
+ try {
41
+ await client.destroy();
42
+ }
43
+ catch {
44
+ // ignore
45
+ }
46
+ client = null;
47
+ }
48
+ logger.warn({ err }, 'Failed to connect Discord RPC');
49
+ return false;
50
+ }
51
+ }
52
+ async function scheduleReconnect() {
53
+ if (destroyed || reconnectTimer)
54
+ return;
55
+ reconnectTimer = setTimeout(async () => {
56
+ reconnectTimer = null;
57
+ if (destroyed)
58
+ return;
59
+ logger.info('Attempting to reconnect to Discord...');
60
+ const ok = await createClient();
61
+ if (!ok)
62
+ scheduleReconnect();
63
+ }, 5000);
64
+ }
65
+ const connected = await createClient();
66
+ if (!connected) {
67
+ throw new Error('Discord is not running. Please open Discord and try again.');
68
+ }
69
+ return {
70
+ get userId() {
71
+ return _userId;
72
+ },
73
+ get isConnected() {
74
+ return _isConnected;
75
+ },
76
+ get rpcClient() {
77
+ return client;
78
+ },
79
+ onConnected(handler) {
80
+ connectedHandlers.push(handler);
81
+ },
82
+ onDisconnected(handler) {
83
+ disconnectedHandlers.push(handler);
84
+ },
85
+ async destroy() {
86
+ destroyed = true;
87
+ if (reconnectTimer)
88
+ clearTimeout(reconnectTimer);
89
+ if (client) {
90
+ try {
91
+ await client.destroy();
92
+ }
93
+ catch {
94
+ // ignore
95
+ }
96
+ client = null;
97
+ }
98
+ _isConnected = false;
99
+ },
100
+ };
101
+ }
102
+ //# sourceMappingURL=discord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord.js","sourceRoot":"","sources":["../src/discord.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { DiscordConnection } from './discord.js';
2
+ import type { ClientConfig } from './config.js';
3
+ export declare function startClient(config: ClientConfig, discord: DiscordConnection): Promise<void>;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8OjG"}
package/dist/index.js ADDED
@@ -0,0 +1,203 @@
1
+ import { logger } from './logger.js';
2
+ import { createTelemetry } from './telemetry/index.js';
3
+ import { SpeedTracker } from './tracker/index.js';
4
+ import { createDetectorState, detectDamageSpike, detectLowFuel, detectSpeeding } from './detectors/index.js';
5
+ import { WebSocketClient } from './ws/index.js';
6
+ import { updateRichPresence, clearRichPresence } from './richpresence/index.js';
7
+ import { TELEMETRY_INTERVAL_MS, RICH_PRESENCE_UPDATE_MS } from './constants.js';
8
+ import { calcTotalTruckDamage, calcFuelPercent } from './utils.js';
9
+ export async function startClient(config, discord) {
10
+ logger.level = config.logLevel;
11
+ logger.info('TruckCord Client starting...');
12
+ const ws = new WebSocketClient(config.wsUrl, discord.userId, config.discordUsername);
13
+ ws.on('auth_ok', () => {
14
+ logger.info('Authenticated with server');
15
+ });
16
+ ws.on('auth_error', (_, data) => {
17
+ logger.error({ data }, 'Authentication failed');
18
+ process.exit(1);
19
+ });
20
+ let running = true;
21
+ let activeJobId = null;
22
+ let isJobInProgress = false;
23
+ let ets2Connected = false;
24
+ let lastRpcUpdate = 0;
25
+ let lastTelemetrySend = 0;
26
+ let lastSpeedingAlert = 0;
27
+ const tracker = new SpeedTracker();
28
+ const detectorState = createDetectorState();
29
+ let telemetryInterval = null;
30
+ const canSendData = () => discord.isConnected && ets2Connected;
31
+ function send(type, data) {
32
+ if (!canSendData()) {
33
+ logger.debug({ type }, 'Blocked send: Discord or ETS2 not connected');
34
+ return;
35
+ }
36
+ ws.send(type, data);
37
+ }
38
+ function gameMinutesToISO(minutes) {
39
+ const hours = Math.floor(minutes / 60);
40
+ const mins = minutes % 60;
41
+ const days = Math.floor(hours / 24);
42
+ const h = hours % 24;
43
+ return `Day ${days + 1} - ${String(h).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
44
+ }
45
+ ws.connect();
46
+ const telemetry = createTelemetry();
47
+ telemetry.on('connected', () => {
48
+ ets2Connected = true;
49
+ });
50
+ telemetry.on('disconnected', () => {
51
+ ets2Connected = false;
52
+ });
53
+ telemetry.on('job-started', (event) => {
54
+ logger.info('Job started');
55
+ activeJobId = crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
56
+ isJobInProgress = true;
57
+ tracker.reset();
58
+ const startTimeReal = new Date().toISOString();
59
+ const now = Date.now();
60
+ const timeAbs = telemetry.data?.current?.timeAbs ?? 0;
61
+ send('job_started', {
62
+ jobId: activeJobId,
63
+ sourceCity: event.citySrc ?? '',
64
+ sourceCompany: event.compSrc ?? '',
65
+ destinationCity: event.cityDst ?? '',
66
+ destinationCompany: event.compDst ?? '',
67
+ cargoName: event.cargo ?? '',
68
+ cargoMass: event.cargoMass ?? 0,
69
+ startTimeReal,
70
+ startTimeGame: gameMinutesToISO(timeAbs),
71
+ });
72
+ updateRichPresence(discord.rpcClient, {
73
+ cargoDestination: event.cityDst,
74
+ cargoName: event.cargo,
75
+ });
76
+ });
77
+ telemetry.on('job-delivered', (event) => {
78
+ logger.info('Job delivered');
79
+ if (!activeJobId)
80
+ return;
81
+ const endTimeReal = new Date().toISOString();
82
+ const timeAbs = telemetry.data?.current?.timeAbs ?? 0;
83
+ const data = telemetry.getData();
84
+ const profit = Number(event.jobDeliveredRevenue ?? 0n);
85
+ const cargoDamage = (event.jobDeliveredCargoDamage ?? 0) * 100;
86
+ send('job_completed', {
87
+ jobId: activeJobId,
88
+ profit,
89
+ maxSpeed: tracker.getMaxSpeed(),
90
+ avgSpeed: tracker.getAverageSpeed(),
91
+ totalDistance: tracker.getTotalDistance(),
92
+ truckDamage: calcTotalTruckDamage({
93
+ engine: data?.wearEngine ?? 0,
94
+ transmission: data?.wearTransmission ?? 0,
95
+ cabin: data?.wearCabin ?? 0,
96
+ chassis: data?.wearChassis ?? 0,
97
+ wheels: data?.wearWheels ?? 0,
98
+ }),
99
+ cargoDamage,
100
+ finesCount: 0,
101
+ endTimeReal,
102
+ endTimeGame: gameMinutesToISO(timeAbs),
103
+ });
104
+ activeJobId = null;
105
+ isJobInProgress = false;
106
+ updateRichPresence(discord.rpcClient, {});
107
+ });
108
+ telemetry.on('job-cancelled', (event) => {
109
+ logger.info('Job cancelled');
110
+ if (!activeJobId)
111
+ return;
112
+ send('job_cancelled', {
113
+ jobId: activeJobId,
114
+ penalty: Number(event.jobCancelledPenalty ?? 0n),
115
+ });
116
+ activeJobId = null;
117
+ isJobInProgress = false;
118
+ updateRichPresence(discord.rpcClient, {});
119
+ });
120
+ telemetry.on('fine', (event) => {
121
+ logger.info({ event }, 'Fine received');
122
+ send('incident', {
123
+ type: 'fine',
124
+ severity: 'low',
125
+ details: {
126
+ offence: event.fineOffence ?? 'Unknown',
127
+ amountEuro: Number(event.fineAmount ?? 0n) / 100,
128
+ },
129
+ });
130
+ });
131
+ telemetryInterval = setInterval(() => {
132
+ if (!running)
133
+ return;
134
+ const data = telemetry.getData();
135
+ if (!data)
136
+ return;
137
+ const now = Date.now();
138
+ const speedKmh = data.speed * 3.6;
139
+ const odometer = data.truckOdometer;
140
+ tracker.update(speedKmh, odometer);
141
+ if (now - lastTelemetrySend >= TELEMETRY_INTERVAL_MS) {
142
+ lastTelemetrySend = now;
143
+ send('telemetry', {
144
+ speed: Math.round(speedKmh * 10) / 10,
145
+ truckDamage: calcTotalTruckDamage({
146
+ engine: data.wearEngine,
147
+ transmission: data.wearTransmission,
148
+ cabin: data.wearCabin,
149
+ chassis: data.wearChassis,
150
+ wheels: data.wearWheels,
151
+ }),
152
+ cargoDamage: data.cargoDamage * 100,
153
+ fuelPercent: calcFuelPercent(data.fuel, data.fuelCapacity),
154
+ totalDistance: tracker.getTotalDistance(),
155
+ odometer: data.truckOdometer,
156
+ isJobActive: isJobInProgress,
157
+ });
158
+ }
159
+ if (now - lastRpcUpdate >= RICH_PRESENCE_UPDATE_MS) {
160
+ lastRpcUpdate = now;
161
+ if (isJobInProgress) {
162
+ updateRichPresence(discord.rpcClient, { speedKmh });
163
+ }
164
+ }
165
+ const damageSpike = detectDamageSpike(data, detectorState);
166
+ if (damageSpike) {
167
+ send('incident', damageSpike);
168
+ }
169
+ const lowFuel = detectLowFuel(data, detectorState);
170
+ if (lowFuel) {
171
+ send('incident', lowFuel);
172
+ }
173
+ if (now - lastSpeedingAlert >= 30000) {
174
+ const speeding = detectSpeeding(data, null);
175
+ if (speeding) {
176
+ lastSpeedingAlert = now;
177
+ send('incident', speeding);
178
+ }
179
+ }
180
+ }, TELEMETRY_INTERVAL_MS);
181
+ discord.onDisconnected(() => {
182
+ logger.warn('Discord disconnected - blocking data sends');
183
+ });
184
+ discord.onConnected(() => {
185
+ logger.info('Discord reconnected - resuming data sends');
186
+ });
187
+ logger.info('TruckCord Client ready - monitoring ETS2 telemetry');
188
+ function shutdown() {
189
+ logger.info('Shutting down...');
190
+ running = false;
191
+ if (telemetryInterval) {
192
+ clearInterval(telemetryInterval);
193
+ telemetryInterval = null;
194
+ }
195
+ clearRichPresence(discord.rpcClient).catch(() => { });
196
+ ws.disconnect();
197
+ telemetry.destroy();
198
+ process.exit(0);
199
+ }
200
+ process.on('SIGINT', shutdown);
201
+ process.on('SIGTERM', shutdown);
202
+ }
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import pino from 'pino';
2
+ export declare const logger: pino.Logger<never, boolean>;
3
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,eAAO,MAAM,MAAM,6BAUjB,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,13 @@
1
+ import pino from 'pino';
2
+ export const logger = pino({
3
+ level: 'info',
4
+ transport: {
5
+ target: 'pino-pretty',
6
+ options: {
7
+ colorize: true,
8
+ translateTime: 'HH:MM:ss',
9
+ ignore: 'pid,hostname',
10
+ },
11
+ },
12
+ });
13
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM;IACb,SAAS,EAAE;QACT,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE;YACP,QAAQ,EAAE,IAAI;YACd,aAAa,EAAE,UAAU;YACzB,MAAM,EAAE,cAAc;SACvB;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Client } from 'discord-rpc';
2
+ export declare function updateRichPresence(rpcClient: Client | null, gameData: {
3
+ speedKmh?: number;
4
+ cargoDestination?: string;
5
+ cargoName?: string;
6
+ }): void;
7
+ export declare function clearRichPresence(rpcClient: Client | null): Promise<void>;
8
+ export declare function getDiscordClient(rpcClient: Client | null): Client | null;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/richpresence/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,IAAI,CAiCN;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAO/E;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAExE"}
@@ -0,0 +1,46 @@
1
+ export function updateRichPresence(rpcClient, gameData) {
2
+ if (!rpcClient)
3
+ return;
4
+ let state = '';
5
+ let details = '';
6
+ if (gameData.cargoDestination) {
7
+ details = `A caminho de ${gameData.cargoDestination}`;
8
+ }
9
+ else {
10
+ details = 'Na estrada';
11
+ }
12
+ if (gameData.cargoName) {
13
+ state = `${gameData.cargoName}`;
14
+ }
15
+ if (gameData.speedKmh !== undefined) {
16
+ state += state ? ` • ${Math.round(gameData.speedKmh)} km/h` : `${Math.round(gameData.speedKmh)} km/h`;
17
+ }
18
+ try {
19
+ rpcClient.setActivity({
20
+ details,
21
+ state: state || undefined,
22
+ largeImageKey: 'ets2_logo',
23
+ largeImageText: 'Euro Truck Simulator 2',
24
+ smallImageKey: 'truck',
25
+ smallImageText: 'TruckCord',
26
+ instance: false,
27
+ });
28
+ }
29
+ catch {
30
+ // Silently ignore RPC errors
31
+ }
32
+ }
33
+ export async function clearRichPresence(rpcClient) {
34
+ if (!rpcClient)
35
+ return;
36
+ try {
37
+ await rpcClient.clearActivity();
38
+ }
39
+ catch {
40
+ // ignore
41
+ }
42
+ }
43
+ export function getDiscordClient(rpcClient) {
44
+ return rpcClient;
45
+ }
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/richpresence/index.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,kBAAkB,CAChC,SAAwB,EACxB,QAIC;IAED,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAC9B,OAAO,GAAG,gBAAgB,QAAQ,CAAC,gBAAgB,EAAE,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,YAAY,CAAC;IACzB,CAAC;IAED,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvB,KAAK,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;IAClC,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACpC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;IACxG,CAAC;IAED,IAAI,CAAC;QACH,SAAS,CAAC,WAAW,CAAC;YACpB,OAAO;YACP,KAAK,EAAE,KAAK,IAAI,SAAS;YACzB,aAAa,EAAE,WAAW;YAC1B,cAAc,EAAE,wBAAwB;YACxC,aAAa,EAAE,OAAO;YACtB,cAAc,EAAE,WAAW;YAC3B,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAwB;IAC9D,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,aAAa,EAAE,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAwB;IACvD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { truckSimTelemetry } from 'trucksim-telemetry';
2
+ import type { SCSSDKTelemetry } from 'trucksim-telemetry';
3
+ type TruckSimTelemetry = ReturnType<typeof truckSimTelemetry>;
4
+ export interface TelemetryHandle {
5
+ readonly data: TruckSimTelemetry['data'];
6
+ readonly isConnected: boolean;
7
+ getData(): SCSSDKTelemetry | null;
8
+ on: TruckSimTelemetry['on'];
9
+ destroy(): void;
10
+ }
11
+ export declare function createTelemetry(): TelemetryHandle;
12
+ export {};
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,OAAO,IAAI,eAAe,GAAG,IAAI,CAAC;IAClC,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,eAAe,IAAI,eAAe,CAkCjD"}
@@ -0,0 +1,34 @@
1
+ import { truckSimTelemetry } from 'trucksim-telemetry';
2
+ import { logger } from '../logger.js';
3
+ export function createTelemetry() {
4
+ const telemetry = truckSimTelemetry();
5
+ let _isConnected = false;
6
+ telemetry.on('connected', () => {
7
+ _isConnected = true;
8
+ logger.info('ETS2 telemetry connected');
9
+ });
10
+ telemetry.on('disconnected', () => {
11
+ _isConnected = false;
12
+ logger.info('ETS2 telemetry disconnected');
13
+ });
14
+ telemetry.on('pause', (paused) => {
15
+ logger.info(`Game ${paused ? 'paused' : 'resumed'}`);
16
+ });
17
+ return {
18
+ get data() {
19
+ return telemetry.data;
20
+ },
21
+ get isConnected() {
22
+ return _isConnected;
23
+ },
24
+ getData() {
25
+ return telemetry.data?.current ?? null;
26
+ },
27
+ on: telemetry.on.bind(telemetry),
28
+ destroy() {
29
+ // trucksim-telemetry doesn't have a destroy method,
30
+ // but we can clean up references
31
+ },
32
+ };
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAYtC,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;IACtC,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QAC7B,YAAY,GAAG,IAAI,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QAChC,YAAY,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,MAAe,EAAE,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,IAAI;YACN,OAAO,SAAS,CAAC,IAAI,CAAC;QACxB,CAAC;QACD,IAAI,WAAW;YACb,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,OAAO;YACL,OAAO,SAAS,CAAC,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC;QACzC,CAAC;QACD,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;QAChC,OAAO;YACL,oDAAoD;YACpD,iCAAiC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare class SpeedTracker {
2
+ private speedSamples;
3
+ private maxSpeed;
4
+ private totalDistance;
5
+ private lastDistance;
6
+ private startTime;
7
+ reset(): void;
8
+ update(speedKmh: number, odometerKm: number): void;
9
+ getMaxSpeed(): number;
10
+ getAverageSpeed(): number;
11
+ getTotalDistance(): number;
12
+ getElapsedMs(): number;
13
+ }
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tracker/index.ts"],"names":[],"mappings":"AAEA,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAuB;IAExC,KAAK,IAAI,IAAI;IAQb,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAwBlD,WAAW,IAAI,MAAM;IAIrB,eAAe,IAAI,MAAM;IAMzB,gBAAgB,IAAI,MAAM;IAI1B,YAAY,IAAI,MAAM;CAIvB"}
@@ -0,0 +1,50 @@
1
+ export class SpeedTracker {
2
+ speedSamples = [];
3
+ maxSpeed = 0;
4
+ totalDistance = 0;
5
+ lastDistance = 0;
6
+ startTime = null;
7
+ reset() {
8
+ this.speedSamples = [];
9
+ this.maxSpeed = 0;
10
+ this.totalDistance = 0;
11
+ this.lastDistance = 0;
12
+ this.startTime = Date.now();
13
+ }
14
+ update(speedKmh, odometerKm) {
15
+ const now = Date.now();
16
+ if (this.startTime === null) {
17
+ this.startTime = now;
18
+ this.lastDistance = odometerKm;
19
+ return;
20
+ }
21
+ this.speedSamples.push({ time: now, speed: speedKmh });
22
+ if (speedKmh > this.maxSpeed) {
23
+ this.maxSpeed = speedKmh;
24
+ }
25
+ if (odometerKm > this.lastDistance) {
26
+ this.totalDistance += odometerKm - this.lastDistance;
27
+ this.lastDistance = odometerKm;
28
+ }
29
+ const cutoff = now - 60000;
30
+ this.speedSamples = this.speedSamples.filter((s) => s.time >= cutoff);
31
+ }
32
+ getMaxSpeed() {
33
+ return Math.round(this.maxSpeed * 10) / 10;
34
+ }
35
+ getAverageSpeed() {
36
+ if (this.speedSamples.length === 0)
37
+ return 0;
38
+ const sum = this.speedSamples.reduce((acc, s) => acc + s.speed, 0);
39
+ return Math.round((sum / this.speedSamples.length) * 10) / 10;
40
+ }
41
+ getTotalDistance() {
42
+ return Math.round(this.totalDistance * 100) / 100;
43
+ }
44
+ getElapsedMs() {
45
+ if (!this.startTime)
46
+ return 0;
47
+ return Date.now() - this.startTime;
48
+ }
49
+ }
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tracker/index.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IACf,YAAY,GAAkB,EAAE,CAAC;IACjC,QAAQ,GAAG,CAAC,CAAC;IACb,aAAa,GAAG,CAAC,CAAC;IAClB,YAAY,GAAG,CAAC,CAAC;IACjB,SAAS,GAAkB,IAAI,CAAC;IAExC,KAAK;QACH,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,CAAC,QAAgB,EAAE,UAAkB;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;YACrB,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEvD,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC3B,CAAC;QAED,IAAI,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,aAAa,IAAI,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC;YACrD,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC;QACjC,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC;IACxE,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAC7C,CAAC;IAED,eAAe;QACb,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAChE,CAAC;IAED,gBAAgB;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IACpD,CAAC;IAED,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/tracker/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SpeedTracker } from './index.js';
3
+ describe('SpeedTracker', () => {
4
+ let tracker;
5
+ beforeEach(() => {
6
+ tracker = new SpeedTracker();
7
+ });
8
+ it('should start with default values', () => {
9
+ expect(tracker.getMaxSpeed()).toBe(0);
10
+ expect(tracker.getAverageSpeed()).toBe(0);
11
+ expect(tracker.getTotalDistance()).toBe(0);
12
+ expect(tracker.getElapsedMs()).toBe(0);
13
+ });
14
+ it('should track max speed', () => {
15
+ tracker.reset();
16
+ tracker.update(50, 0);
17
+ tracker.update(100, 1);
18
+ tracker.update(75, 2);
19
+ expect(tracker.getMaxSpeed()).toBe(100);
20
+ });
21
+ it('should track total distance', () => {
22
+ tracker.reset();
23
+ tracker.update(50, 0);
24
+ tracker.update(60, 10);
25
+ tracker.update(70, 25);
26
+ expect(tracker.getTotalDistance()).toBe(25);
27
+ });
28
+ it('should handle odometer changes correctly after first update', () => {
29
+ tracker.reset();
30
+ tracker.update(50, 0);
31
+ tracker.update(60, 50);
32
+ expect(tracker.getTotalDistance()).toBe(50);
33
+ });
34
+ it('should calculate average speed', () => {
35
+ tracker.reset();
36
+ tracker.update(50, 0);
37
+ tracker.update(100, 1);
38
+ tracker.update(75, 2);
39
+ tracker.update(25, 3);
40
+ expect(tracker.getAverageSpeed()).toBe(62.5);
41
+ });
42
+ it('should reset all values', () => {
43
+ tracker.reset();
44
+ tracker.update(80, 10);
45
+ tracker.reset();
46
+ expect(tracker.getMaxSpeed()).toBe(0);
47
+ expect(tracker.getTotalDistance()).toBe(0);
48
+ });
49
+ it('should track elapsed time', () => {
50
+ tracker.reset();
51
+ expect(tracker.getElapsedMs()).toBeGreaterThanOrEqual(0);
52
+ });
53
+ });
54
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/tracker/index.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ interface WearComponents {
2
+ engine: number;
3
+ transmission: number;
4
+ cabin: number;
5
+ chassis: number;
6
+ wheels: number;
7
+ }
8
+ export declare function calcTotalTruckDamage(wear: WearComponents): number;
9
+ export declare function calcFuelPercent(current: number, capacity: number): number;
10
+ export {};
11
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAGjE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzE"}
package/dist/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ export function calcTotalTruckDamage(wear) {
2
+ const avg = (wear.engine + wear.transmission + wear.cabin + wear.chassis + wear.wheels) / 5;
3
+ return Math.round(avg * 100 * 100) / 100;
4
+ }
5
+ export function calcFuelPercent(current, capacity) {
6
+ if (capacity <= 0)
7
+ return 0;
8
+ return Math.round((current / capacity) * 100);
9
+ }
10
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,oBAAoB,CAAC,IAAoB;IACvD,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5F,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,QAAgB;IAC/D,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,20 @@
1
+ type MessageHandler = (type: string, data: Record<string, unknown>) => void;
2
+ export declare class WebSocketClient {
3
+ private ws;
4
+ private url;
5
+ private discordId;
6
+ private username;
7
+ private handlers;
8
+ private reconnectAttempts;
9
+ private reconnectTimer;
10
+ private connected;
11
+ constructor(url: string, discordId: string, username: string);
12
+ connect(): void;
13
+ private scheduleReconnect;
14
+ on(type: string, handler: MessageHandler): void;
15
+ send(type: string, data: object): void;
16
+ isConnected(): boolean;
17
+ disconnect(): void;
18
+ }
19
+ export {};
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ws/index.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAE5E,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,SAAS,CAAS;gBAEd,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAM5D,OAAO,IAAI,IAAI;IA2Cf,OAAO,CAAC,iBAAiB;IAazB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAI/C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAStC,WAAW,IAAI,OAAO;IAItB,UAAU,IAAI,IAAI;CAUnB"}
@@ -0,0 +1,88 @@
1
+ import WebSocket from 'ws';
2
+ import { logger } from '../logger.js';
3
+ import { WS_RECONNECT_INTERVAL_MS, WS_RECONNECT_MAX_ATTEMPTS } from '../constants.js';
4
+ export class WebSocketClient {
5
+ ws = null;
6
+ url;
7
+ discordId;
8
+ username;
9
+ handlers = new Map();
10
+ reconnectAttempts = 0;
11
+ reconnectTimer = null;
12
+ connected = false;
13
+ constructor(url, discordId, username) {
14
+ this.url = url;
15
+ this.discordId = discordId;
16
+ this.username = username;
17
+ }
18
+ connect() {
19
+ if (this.ws) {
20
+ this.ws.close();
21
+ }
22
+ logger.info(`Connecting to ${this.url}`);
23
+ this.ws = new WebSocket(this.url);
24
+ this.ws.on('open', () => {
25
+ logger.info('WebSocket connected');
26
+ this.connected = true;
27
+ this.reconnectAttempts = 0;
28
+ this.send('auth', {
29
+ discordId: this.discordId,
30
+ username: this.username,
31
+ });
32
+ });
33
+ this.ws.on('message', (raw) => {
34
+ try {
35
+ const message = JSON.parse(raw.toString());
36
+ const handler = this.handlers.get(message.type);
37
+ if (handler) {
38
+ handler(message.type, message.data ?? {});
39
+ }
40
+ }
41
+ catch {
42
+ logger.error('Failed to parse server message');
43
+ }
44
+ });
45
+ this.ws.on('close', (code) => {
46
+ this.connected = false;
47
+ logger.warn({ code }, 'WebSocket disconnected');
48
+ this.scheduleReconnect();
49
+ });
50
+ this.ws.on('error', (err) => {
51
+ logger.error({ err }, 'WebSocket error');
52
+ });
53
+ }
54
+ scheduleReconnect() {
55
+ if (this.reconnectAttempts >= WS_RECONNECT_MAX_ATTEMPTS) {
56
+ logger.error('Max reconnect attempts reached');
57
+ return;
58
+ }
59
+ this.reconnectAttempts++;
60
+ const delay = WS_RECONNECT_INTERVAL_MS * this.reconnectAttempts;
61
+ logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
62
+ this.reconnectTimer = setTimeout(() => this.connect(), delay);
63
+ }
64
+ on(type, handler) {
65
+ this.handlers.set(type, handler);
66
+ }
67
+ send(type, data) {
68
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
69
+ logger.warn(`Cannot send ${type}: not connected`);
70
+ return;
71
+ }
72
+ this.ws.send(JSON.stringify({ type, data }));
73
+ }
74
+ isConnected() {
75
+ return this.connected;
76
+ }
77
+ disconnect() {
78
+ if (this.reconnectTimer) {
79
+ clearTimeout(this.reconnectTimer);
80
+ }
81
+ if (this.ws) {
82
+ this.ws.close();
83
+ this.ws = null;
84
+ }
85
+ this.connected = false;
86
+ }
87
+ }
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ws/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "truckcord-client",
3
+ "version": "1.0.3",
4
+ "description": "ETS2/ATS telemetry client for TruckCord Discord integration",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "truckcord-client": "./dist/bin.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "tsx --watch src/bin.ts",
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "typecheck": "tsc --noEmit",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "discord-rpc": "^4.0.1",
24
+ "pino": "^9.6.0",
25
+ "pino-pretty": "^13.0.0",
26
+ "trucksim-telemetry": "^1.0.0",
27
+ "ws": "^8.18.2",
28
+ "zod": "^3.24.3"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.14.1",
32
+ "@types/ws": "^8.18.1",
33
+ "tsx": "^4.19.4",
34
+ "typescript": "^5.8.3",
35
+ "vitest": "^3.1.2"
36
+ }
37
+ }