libroadcast-cli 2.3.0 → 2.4.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
@@ -82,6 +82,12 @@ Commands:
82
82
  Options:
83
83
  --loop <intervalInSeconds> Continuously push the PGN file at the specified interval in seconds.
84
84
 
85
+ pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>]
86
+ Upload a PGN file from a local path or URL to the specified broadcast round, filtering games by FIDE ID.
87
+ Note: The PGN file must be accessible from the provided path or URL.
88
+ Options:
89
+ --loop <intervalInSeconds> Continuously push the PGN file at the specified interval in seconds.
90
+
85
91
 
86
92
  Examples:
87
93
  # Login with your Lichess token (interactive)
@@ -108,4 +114,6 @@ Examples:
108
114
  $ score bcast123 1.0 0.5 1.0 0.5
109
115
  # Push a PGN file in loop mode every 60 seconds
110
116
  $ push round456 /path/to/localfile.pgn --loop 60
117
+ # Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds
118
+ $ pushFilterID round456 https://example.com/games.pgn 12345 67890 --loop 120
111
119
  ```
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.pushFilterIDCommand = void 0;
7
+ const node_process_1 = require("node:process");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const commandHandler_1 = require("../utils/commandHandler");
11
+ const pgn_1 = require("chessops/pgn");
12
+ const getInfoBroadcast_1 = require("../utils/getInfoBroadcast");
13
+ const colors_1 = __importDefault(require("../utils/colors"));
14
+ const pushPGN = async (round, pgn) => {
15
+ try {
16
+ const res = await commandHandler_1.client
17
+ .POST("/api/broadcast/round/{broadcastRoundId}/push", {
18
+ params: {
19
+ path: { broadcastRoundId: round.id },
20
+ },
21
+ body: pgn,
22
+ bodySerializer: (body) => body,
23
+ })
24
+ .then((response) => response.data);
25
+ console.log(colors_1.default.green(`✓ Successfully pushed PGN for round ${colors_1.default.whiteBold(round.id)}.`));
26
+ console.table(res?.games.map((game, i) => {
27
+ return {
28
+ "Game #": i + 1,
29
+ "White Player": game.tags["White"] || "Unknown",
30
+ "Black Player": game.tags["Black"] || "Unknown",
31
+ Result: game.tags["Result"] || "Unknown",
32
+ "Ply Count": game.moves || "Unknown",
33
+ Error: game.error || "None",
34
+ };
35
+ }));
36
+ }
37
+ catch (error) {
38
+ console.error(colors_1.default.red(`Error pushing PGN for round ${colors_1.default.whiteBold(round.id)}:`), error);
39
+ }
40
+ };
41
+ const filterPgnByIds = (pgn, filterIds) => {
42
+ const parsed = (0, pgn_1.parsePgn)(pgn);
43
+ const filteredGames = parsed.filter((game) => {
44
+ const whiteFideId = game.headers.get("WhiteFideId");
45
+ const blackFideId = game.headers.get("BlackFideId");
46
+ if (filterIds.length > 1) {
47
+ for (let i = 0; i < filterIds.length; i += 2) {
48
+ const whiteId = filterIds[i];
49
+ const blackId = filterIds[i + 1];
50
+ if ((whiteFideId === String(whiteId) &&
51
+ blackFideId === String(blackId)) ||
52
+ (whiteFideId === String(blackId) && blackFideId === String(whiteId))) {
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ else {
59
+ return (filterIds.includes(parseInt(whiteFideId || "", 10)) ||
60
+ filterIds.includes(parseInt(blackFideId || "", 10)));
61
+ }
62
+ });
63
+ return filteredGames.map((game) => (0, pgn_1.makePgn)(game)).join("\n\n");
64
+ };
65
+ const readPGNFromURL = async (pgnURL, filterIds) => {
66
+ if (pgnURL.startsWith("http://") || pgnURL.startsWith("https://")) {
67
+ const response = await fetch(pgnURL, {
68
+ method: "GET",
69
+ headers: {
70
+ "User-Agent": commandHandler_1.packageJson.name + "/" + commandHandler_1.packageJson.version,
71
+ },
72
+ });
73
+ if (!response.ok) {
74
+ console.error(colors_1.default.red(`Failed to fetch PGN from URL: ${response.statusText}`));
75
+ return undefined;
76
+ }
77
+ const pgnText = await response.text();
78
+ const filteredPgn = filterPgnByIds(pgnText, filterIds);
79
+ if (!filteredPgn) {
80
+ console.error(colors_1.default.red(`No games found matching the provided filter IDs.`));
81
+ return undefined;
82
+ }
83
+ return filteredPgn;
84
+ }
85
+ else {
86
+ const resolvedPath = node_path_1.default.resolve(pgnURL);
87
+ const stats = await (0, promises_1.readFile)(resolvedPath, { encoding: "utf-8" }).catch((err) => {
88
+ console.error(colors_1.default.red(`Failed to read PGN file: ${err.message}`));
89
+ return undefined;
90
+ });
91
+ if (!stats)
92
+ return undefined;
93
+ return stats.toString();
94
+ }
95
+ };
96
+ const loop = async (roundInfo, pgnPath, loopTimer, filterIds) => {
97
+ while (true) {
98
+ const pgnContent = await readPGNFromURL(pgnPath, filterIds);
99
+ if (pgnContent)
100
+ await pushPGN(roundInfo, pgnContent);
101
+ await (0, commandHandler_1.sleep)(loopTimer * 1000);
102
+ }
103
+ };
104
+ const pushFilterIDCommand = async (args) => {
105
+ await (0, commandHandler_1.checkTokenScopes)();
106
+ const [roundId, pgnPath] = args.slice(0, 2);
107
+ const filterIds = args
108
+ .slice(2)
109
+ .filter((arg) => !arg.startsWith("--") && Number.isInteger(parseInt(arg)))
110
+ .map((arg) => parseInt(arg, 10));
111
+ if (!roundId || !pgnPath || filterIds.length === 0) {
112
+ (0, commandHandler_1.msgCommonErrorHelp)("Round ID, PGN, and at least one filter ID are required.");
113
+ (0, node_process_1.exit)(1);
114
+ }
115
+ const roundInfo = await (0, getInfoBroadcast_1.getBroadcastRound)(roundId);
116
+ if (!roundInfo) {
117
+ console.error(colors_1.default.red("Round not found."));
118
+ (0, node_process_1.exit)(1);
119
+ }
120
+ const loopArgIndex = args.findIndex((arg) => arg === "--loop");
121
+ let loopTimer = undefined;
122
+ if (loopArgIndex !== -1 && loopArgIndex + 1 < args.length) {
123
+ const loopTimerStr = args[loopArgIndex + 1];
124
+ loopTimer = parseInt(loopTimerStr, 10);
125
+ if (isNaN(loopTimer) || loopTimer <= 0) {
126
+ console.error(colors_1.default.red("Loop timer must be a positive integer."));
127
+ (0, node_process_1.exit)(1);
128
+ }
129
+ }
130
+ if (loopTimer) {
131
+ console.log(colors_1.default.green(`Starting loop to push PGN every ${colors_1.default.whiteBold(loopTimer.toString())} seconds...`));
132
+ console.log(colors_1.default.blue("Press Ctrl+C to stop."));
133
+ await loop(roundInfo, pgnPath, loopTimer, filterIds);
134
+ }
135
+ else {
136
+ const pgnContent = await readPGNFromURL(pgnPath, filterIds);
137
+ if (pgnContent)
138
+ await pushPGN(roundInfo, pgnContent);
139
+ }
140
+ };
141
+ exports.pushFilterIDCommand = pushFilterIDCommand;
@@ -18,6 +18,7 @@ const startsPrevious_1 = require("../cmd/startsPrevious");
18
18
  const period_1 = require("../cmd/period");
19
19
  const score_1 = require("../cmd/score");
20
20
  const push_1 = require("../cmd/push");
21
+ const pushFilterID_1 = require("../cmd/pushFilterID");
21
22
  const login_1 = require("../cmd/login");
22
23
  const credentials_1 = require("./credentials");
23
24
  const getToken = () => {
@@ -47,6 +48,7 @@ var Command;
47
48
  Command["Period"] = "period";
48
49
  Command["Score"] = "score";
49
50
  Command["Push"] = "push";
51
+ Command["PushFilterID"] = "pushFilterID";
50
52
  })(Command || (exports.Command = Command = {}));
51
53
  exports.commands = new Map([
52
54
  [Command.Login, login_1.loginCommand],
@@ -59,6 +61,7 @@ exports.commands = new Map([
59
61
  [Command.Period, period_1.periodCommand],
60
62
  [Command.Score, score_1.scoreCommand],
61
63
  [Command.Push, push_1.pushCommand],
64
+ [Command.PushFilterID, pushFilterID_1.pushFilterIDCommand],
62
65
  ]);
63
66
  exports.client = (0, openapi_fetch_1.default)({
64
67
  baseUrl: LICHESS_DOMAIN,
@@ -81,6 +81,13 @@ const helpPush = [
81
81
  ` ${colors_1.default.bold("Options:")}`,
82
82
  ` --loop <intervalInSeconds> ${colors_1.default.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
83
83
  ].join("\n");
84
+ const helpPushFilterID = [
85
+ ` ${colors_1.default.underItalic("pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>]")}`,
86
+ ` ${colors_1.default.gray("Upload a PGN file from a local path or URL to the specified broadcast round, filtering games by FIDE ID.")}`,
87
+ ` ${colors_1.default.bold("Note:")} ${colors_1.default.gray("The PGN file must be accessible from the provided path or URL.")}`,
88
+ ` ${colors_1.default.bold("Options:")}`,
89
+ ` --loop <intervalInSeconds> ${colors_1.default.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
90
+ ].join("\n");
84
91
  const msg = [
85
92
  `${colors_1.default.boldYellow("Usage:")} ${colors_1.default.underItalic("<command> [options]")}`,
86
93
  ``,
@@ -106,6 +113,8 @@ const msg = [
106
113
  ``,
107
114
  helpPush,
108
115
  ``,
116
+ helpPushFilterID,
117
+ ``,
109
118
  ``,
110
119
  `${colors_1.default.boldYellow("Examples:")}`,
111
120
  ` ${colors_1.default.gray("# Login with your Lichess token (interactive)")}`,
@@ -132,6 +141,8 @@ const msg = [
132
141
  ` $ ${colors_1.default.underItalic("score")} ${colors_1.default.italic("bcast123 1.0 0.5 1.0 0.5")}`,
133
142
  ` ${colors_1.default.gray("# Push a PGN file in loop mode every 60 seconds")}`,
134
143
  ` $ ${colors_1.default.underItalic("push")} ${colors_1.default.italic("round456 /path/to/localfile.pgn --loop 60")}`,
144
+ ` ${colors_1.default.gray("# Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds")}`,
145
+ ` $ ${colors_1.default.underItalic("pushFilterID")} ${colors_1.default.italic("round456 https://example.com/games.pgn 12345 67890 --loop 120")}`,
135
146
  ];
136
147
  const showHelp = (cmd) => {
137
148
  const ranges = {
@@ -145,6 +156,7 @@ const showHelp = (cmd) => {
145
156
  [commandHandler_1.Command.Period]: helpSetPeriod,
146
157
  [commandHandler_1.Command.Score]: helpScore,
147
158
  [commandHandler_1.Command.Push]: helpPush,
159
+ [commandHandler_1.Command.PushFilterID]: helpPushFilterID,
148
160
  };
149
161
  const range = cmd ? ranges[cmd] : undefined;
150
162
  console.info(range ? range : msg.join("\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libroadcast-cli",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "CLI to help with broadcast maintenance on Lichess",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [
@@ -14,11 +14,12 @@
14
14
  "dependencies": {
15
15
  "@lichess-org/types": "^2.0.109",
16
16
  "@types/node": "~24.10.4",
17
+ "chessops": "^0.15.0",
17
18
  "ms": "3.0.0-canary.202508261828",
18
19
  "openapi-fetch": "^0.15.0"
19
20
  },
20
21
  "devDependencies": {
21
- "prettier": "3.7.4",
22
+ "prettier": "3.8.1",
22
23
  "typescript": "^5.9.3"
23
24
  },
24
25
  "engines": {