@ekmanss/5eplay 20260716.0.0 → 20260716.1.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/README.md CHANGED
@@ -99,7 +99,7 @@ The human-readable report is deliberately more compact than the typed snapshot:
99
99
  - the overview keeps only match status/identity/version/format, capture time, and source URL;
100
100
  - completed and live maps show scores, clear half/round results, player tables, duels, highlights,
101
101
  milestones, and readable logs; upcoming maps use one short placeholder;
102
- - head-to-head and recent matches show only time, teams/opponent, score, and result;
102
+ - head-to-head and recent matches show only time, canonical match link, teams/opponent, score, and result;
103
103
  - player power is reduced to Rating plus firepower, entry, opening, utility, sniping, clutch, and
104
104
  trading scores;
105
105
  - transport versions, coordinates, raw round codes, provider JSON, odds, logos, and community
package/dist/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export { createFiveEPlayMatchSession } from './session.js';
7
7
  export { fiveEPlayMarkdownOutputPath, writeFiveEPlayMatchMarkdown, } from './write_markdown.js';
8
8
  export type { FiveEPlayMarkdownOptions } from './markdown.js';
9
9
  export type { WriteFiveEPlayMatchMarkdownOptions, WriteFiveEPlayMatchMarkdownResult, } from './write_markdown.js';
10
- export type { CreateFiveEPlayMatchSessionOptions, FiveEPlayAnalysisMap, FiveEPlayAnalysisPlayer, FiveEPlayBombEvent, FiveEPlayClientOptions, FiveEPlayCommunityCard, FiveEPlayCommunityRatings, FiveEPlayCommunityScore, FiveEPlayDiagnosticWarning, FiveEPlayErrorCode, FiveEPlayHalfScore, FiveEPlayJson, FiveEPlayJsonObject, FiveEPlayLiveMatch, FiveEPlayLiveMatchMap, FiveEPlayLiveMatchesData, FiveEPlayLiveMatchesDiagnostics, FiveEPlayLiveMatchTeam, FiveEPlayKillEvent, FiveEPlayLogEvent, FiveEPlayLogPlayer, FiveEPlayMap, FiveEPlayMapStatus, FiveEPlayMatch, FiveEPlayMatchDiagnostics, FiveEPlayMatchIdentity, FiveEPlayMatchSession, FiveEPlayMatchStatus, FiveEPlayOperation, FiveEPlayPlayerDuel, FiveEPlayPlayerEquipment, FiveEPlayPlayerMetrics, FiveEPlayPlayerStats, FiveEPlayPrematchAnalysis, FiveEPlayProgressEvent, FiveEPlayRealtimeUpdate, FiveEPlayRequestDiagnostic, FiveEPlayRequestOptions, FiveEPlayRoundEnd, FiveEPlayRoundStart, FiveEPlaySide, FiveEPlayStage, FiveEPlayTeam, FiveEPlayTeamMapState, FiveEPlayTeamPlayerStats, FiveEPlayTournament, FiveEPlayVetoEntry, FiveEPlayWebSocketFactory, FiveEPlayWebSocketLike, GetFiveEPlayMatchOptions, GetFiveEPlayMatchResult, GetFiveEPlayLiveMatchesOptions, GetFiveEPlayLiveMatchesResult, } from './types.js';
10
+ export type { CreateFiveEPlayMatchSessionOptions, FiveEPlayAnalysisMap, FiveEPlayAnalysisPlayer, FiveEPlayBombEvent, FiveEPlayClientOptions, FiveEPlayCommunityCard, FiveEPlayCommunityRatings, FiveEPlayCommunityScore, FiveEPlayDiagnosticWarning, FiveEPlayErrorCode, FiveEPlayHalfScore, FiveEPlayJson, FiveEPlayJsonObject, FiveEPlayLiveMatch, FiveEPlayLiveMatchMap, FiveEPlayLiveMatchesData, FiveEPlayLiveMatchesDiagnostics, FiveEPlayLiveMatchTeam, FiveEPlayKillEvent, FiveEPlayLogEvent, FiveEPlayLogPlayer, FiveEPlayMap, FiveEPlayMapStatus, FiveEPlayMatch, FiveEPlayMatchDiagnostics, FiveEPlayMatchIdentity, FiveEPlayMatchSession, FiveEPlayMatchStatus, FiveEPlayOperation, FiveEPlayPlayerDuel, FiveEPlayPlayerEquipment, FiveEPlayPlayerMetrics, FiveEPlayPlayerStats, FiveEPlayPrematchAnalysis, FiveEPlayProgressEvent, FiveEPlayRealtimeUpdate, FiveEPlayRecentMatchReference, FiveEPlayRecentMatchTeam, FiveEPlayRequestDiagnostic, FiveEPlayRequestOptions, FiveEPlayRoundEnd, FiveEPlayRoundStart, FiveEPlaySide, FiveEPlayStage, FiveEPlayTeam, FiveEPlayTeamMapState, FiveEPlayTeamPlayerStats, FiveEPlayTeamRecentMatches, FiveEPlayTournament, FiveEPlayVetoEntry, FiveEPlayWebSocketFactory, FiveEPlayWebSocketLike, GetFiveEPlayMatchOptions, GetFiveEPlayMatchResult, GetFiveEPlayLiveMatchesOptions, GetFiveEPlayLiveMatchesResult, } from './types.js';
package/dist/markdown.js CHANGED
@@ -342,25 +342,24 @@ function recentMatchesSection(match) {
342
342
  return [];
343
343
  const lines = ['### 近期比赛', ''];
344
344
  for (const item of analysis.recentMatches) {
345
- const rows = item.matches.flatMap((group) => records(record(group).matches)).map((game) => {
346
- const home = record(game.home_info);
347
- const opponent = record(game.opponent_info);
348
- const teamIsHome = text(home.id) === item.teamId || text(opponent.id) !== item.teamId;
349
- const teamScore = integer(game[teamIsHome ? 'home_score' : 'opponent_score']);
350
- const opponentScore = integer(game[teamIsHome ? 'opponent_score' : 'home_score']);
351
- const opponentName = text((teamIsHome ? opponent : home).disp_name);
352
- const outcome = teamScore === null || opponentScore === null ? '—'
353
- : teamScore > opponentScore ? '胜' : teamScore < opponentScore ? '负' : '平';
345
+ const rows = item.matches.map((game) => {
346
+ const team = game.teams.find((candidate) => candidate.id === item.teamId);
347
+ const opponent = game.teams.find((candidate) => candidate.id !== item.teamId);
348
+ const outcome = game.winnerTeamId === null ? '平'
349
+ : game.winnerTeamId === item.teamId ? '' : '';
354
350
  return [
355
- compactTimestamp(integer(game.ts)),
356
- opponentName,
357
- `${number(teamScore)} : ${number(opponentScore)}`,
351
+ compactTimestamp(game.playedAtUnixSeconds),
352
+ `[${game.id}](${game.url})`,
353
+ opponent?.name,
354
+ `${number(team?.score ?? null)} : ${number(opponent?.score ?? null)}`,
358
355
  outcome,
359
356
  ];
360
357
  });
361
358
  lines.push(`#### ${plain(teamName(match, item.teamId))}`, '', rows.length
362
- ? table(['比赛时间', '对手', '比分(本队 : 对手)', '结果'], rows)
363
- : '_暂无近期比赛数据_', '');
359
+ ? table(['比赛时间', '比赛', '对手', '比分(本队 : 对手)', '结果'], rows)
360
+ : '_暂无近期比赛数据_', item.invalidReferenceCount > 0
361
+ ? `> 注意:源数据中另有 ${item.invalidReferenceCount} 条近期比赛引用不完整,未纳入报告。`
362
+ : '', '');
364
363
  }
365
364
  return lines;
366
365
  }
package/dist/transform.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { mergeLogEvents, transformLogRecord } from './log.js';
2
+ import { matchIdentityFromInput } from './input.js';
2
3
  import { flag, integer, jsonObjects, numeric, record, records, side, strings, text, } from './value.js';
3
4
  function cleanName(value) {
4
5
  return text(value) ?? '';
@@ -298,6 +299,52 @@ function analysisMap(value, teams) {
298
299
  }),
299
300
  };
300
301
  }
302
+ function recentMatchRows(value) {
303
+ return records(record(value).matches).flatMap((group) => {
304
+ const nested = records(group.matches);
305
+ return nested.length ? nested : text(group.id) ? [group] : [];
306
+ });
307
+ }
308
+ function completedRecentMatch(value) {
309
+ const source = record(value);
310
+ const identity = matchIdentityFromInput(text(source.id) ?? '');
311
+ const status = text(source.status)?.toLowerCase();
312
+ const playedAtUnixSeconds = integer(source.ts);
313
+ const first = record(source.home_info);
314
+ const second = record(source.opponent_info);
315
+ const firstId = text(first.id);
316
+ const secondId = text(second.id);
317
+ const firstName = text(first.disp_name);
318
+ const secondName = text(second.disp_name);
319
+ const firstScore = integer(source.home_score);
320
+ const secondScore = integer(source.opponent_score);
321
+ const completed = status === 'past' || status === 'completed' || status === '2';
322
+ if (!identity || !completed || playedAtUnixSeconds === null || playedAtUnixSeconds <= 0
323
+ || !firstId || !secondId || !firstName || !secondName
324
+ || firstScore === null || secondScore === null || firstScore < 0 || secondScore < 0) {
325
+ return null;
326
+ }
327
+ return {
328
+ ...identity,
329
+ status: 'completed',
330
+ playedAtUnixSeconds,
331
+ teams: [
332
+ { id: firstId, name: firstName, score: firstScore },
333
+ { id: secondId, name: secondName, score: secondScore },
334
+ ],
335
+ winnerTeamId: firstScore === secondScore ? null : firstScore > secondScore ? firstId : secondId,
336
+ };
337
+ }
338
+ function recentMatches(value) {
339
+ const rows = recentMatchRows(value);
340
+ const normalized = rows.map(completedRecentMatch);
341
+ const byId = new Map(normalized.flatMap((match) => match ? [[match.id, match]] : []));
342
+ return {
343
+ sourceCount: rows.length,
344
+ invalidReferenceCount: normalized.filter((match) => match === null).length,
345
+ matches: [...byId.values()].sort((left, right) => right.playedAtUnixSeconds - left.playedAtUnixSeconds),
346
+ };
347
+ }
301
348
  function prematchAnalysis(value, teams) {
302
349
  if (value === null)
303
350
  return null;
@@ -330,7 +377,7 @@ function prematchAnalysis(value, teams) {
330
377
  })),
331
378
  recentMatches: teams.map((team, index) => ({
332
379
  teamId: team.id,
333
- matches: jsonObjects(record(result[index === 0 ? 't1_rec_matches' : 't2_rec_matches']).matches),
380
+ ...recentMatches(result[index === 0 ? 't1_rec_matches' : 't2_rec_matches']),
334
381
  })),
335
382
  headToHead: {
336
383
  teamWinRates: teams.map((team, index) => ({
package/dist/types.d.ts CHANGED
@@ -293,6 +293,26 @@ export interface FiveEPlayAnalysisMap {
293
293
  banRate: number | null;
294
294
  }>;
295
295
  }
296
+ export interface FiveEPlayRecentMatchTeam {
297
+ id: string;
298
+ name: string;
299
+ score: number;
300
+ }
301
+ export interface FiveEPlayRecentMatchReference {
302
+ id: string;
303
+ numericId: number;
304
+ url: string;
305
+ status: 'completed';
306
+ playedAtUnixSeconds: number;
307
+ teams: [FiveEPlayRecentMatchTeam, FiveEPlayRecentMatchTeam];
308
+ winnerTeamId: string | null;
309
+ }
310
+ export interface FiveEPlayTeamRecentMatches {
311
+ teamId: string;
312
+ sourceCount: number;
313
+ invalidReferenceCount: number;
314
+ matches: FiveEPlayRecentMatchReference[];
315
+ }
296
316
  export interface FiveEPlayPrematchAnalysis {
297
317
  hidden: boolean;
298
318
  teams: Array<{
@@ -309,10 +329,7 @@ export interface FiveEPlayPrematchAnalysis {
309
329
  teamId: string;
310
330
  players: FiveEPlayJsonObject[];
311
331
  }>;
312
- recentMatches: Array<{
313
- teamId: string;
314
- matches: FiveEPlayJsonObject[];
315
- }>;
332
+ recentMatches: FiveEPlayTeamRecentMatches[];
316
333
  headToHead: {
317
334
  teamWinRates: Array<{
318
335
  teamId: string;
@@ -44,6 +44,12 @@ weapon, special-kill flag, and coordinate fields. Unknown future event types rem
44
44
  records, recent matches, and head-to-head matches. Provider-specific power records remain JSON
45
45
  objects so new metrics are preserved without a schema bump.
46
46
 
47
+ Each `analysis.recentMatches[]` group exposes typed, completed match references rather than raw
48
+ provider JSON. Every reference contains its canonical `csgo_mc_*` ID/URL, numeric ID, completion
49
+ status and timestamp, both teams and scores, and the winning team ID. `sourceCount` records how
50
+ many source rows were present; `invalidReferenceCount` makes malformed or incomplete source rows
51
+ explicit so consumers can fail closed instead of treating them as genuinely missing history.
52
+
47
53
  `communityRatings` contains every public tab and returned card, including player, coach, and
48
54
  big-event cards. Account-specific `my_*` voting state, write actions, and chat messages are not
49
55
  returned.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekmanss/5eplay",
3
- "version": "20260716.0.0",
3
+ "version": "20260716.1.0",
4
4
  "description": "Typed 5EPlay CS2 live-match discovery, match-detail snapshots, and MQTT updates without a browser.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",