libroadcast-cli 2.6.0 → 2.8.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
@@ -76,12 +76,15 @@ Commands:
76
76
  Options:
77
77
  --loop <intervalInSeconds> Continuously push the PGN file at the specified interval in seconds.
78
78
 
79
- pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>] [--firstOngoing]
79
+ pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>]
80
80
  Upload a PGN file from a local path or URL to the specified broadcast round, filtering games by FIDE ID.
81
81
  Note: The PGN file must be accessible from the provided path or URL.
82
82
  Options:
83
83
  --loop <intervalInSeconds> Continuously push the PGN file at the specified interval in seconds.
84
- --firstOngoing Push the first ongoing games in each round.
84
+
85
+ convertNamesToID <roundId>
86
+ Convert Lichess Usernames source to Lichess Game IDs source for the specified broadcast round.
87
+ Note: This command will fetch the source PGN, extract Lichess Game IDs from the PGN headers, and update the broadcast round to use these game IDs as the source.
85
88
 
86
89
 
87
90
  Examples:
@@ -111,6 +114,8 @@ Examples:
111
114
  $ push round456 /path/to/localfile.pgn --loop 60
112
115
  # Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds
113
116
  $ pushFilterID round456 https://example.com/games.pgn 12345 67890 --loop 120
117
+ # Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round
118
+ $ convertNamesToID round456
114
119
  ```
115
120
 
116
121
  ### Test Docker Build Locally
@@ -0,0 +1,50 @@
1
+ import { exit } from "node:process";
2
+ import { msgCommonErrorHelp, checkTokenScopes, client, handleApiResponse, } from "../utils/commandHandler.js";
3
+ import { parsePgn } from "chessops/pgn";
4
+ import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
5
+ import cl from "../utils/colors.js";
6
+ const setGameIds = (roundInfo, gameIds) => handleApiResponse(client.POST("/broadcast/round/{broadcastRoundId}/edit", {
7
+ params: {
8
+ path: { broadcastRoundId: roundInfo.id },
9
+ query: { patch: 1 },
10
+ },
11
+ body: {
12
+ syncSource: "ids",
13
+ syncIds: gameIds,
14
+ },
15
+ }), `Successfully set game IDs for round ${cl.whiteBold(roundInfo.id)}.`, `Error setting game IDs for round ${cl.whiteBold(roundInfo.id)}`);
16
+ const getGameIdFromPgn = async (roundId) => {
17
+ const response = await client.GET("/api/broadcast/round/{broadcastRoundId}.pgn", {
18
+ params: { path: { broadcastRoundId: roundId } },
19
+ parseAs: "text",
20
+ });
21
+ if (!response.response.ok) {
22
+ console.error(cl.red("Failed to fetch PGN for the round."));
23
+ exit(1);
24
+ }
25
+ const pgn = parsePgn(response.data);
26
+ const gamesWithIds = pgn
27
+ .map((game) => game.headers.get("GameId") || null)
28
+ .filter((id) => typeof id === "string");
29
+ return gamesWithIds;
30
+ };
31
+ export const convertNamesToIDCommand = async (args) => {
32
+ await checkTokenScopes();
33
+ const [roundId] = args.slice(0, 1);
34
+ if (!roundId) {
35
+ msgCommonErrorHelp("Round ID is required.");
36
+ exit(1);
37
+ }
38
+ const roundInfo = await getBroadcastRound(roundId);
39
+ if (!roundInfo) {
40
+ console.error(cl.red("Round not found."));
41
+ exit(1);
42
+ }
43
+ const gameIds = await getGameIdFromPgn(roundId);
44
+ if (gameIds.length === 0) {
45
+ console.error(cl.red("No games with GameId found in the PGN."));
46
+ exit(1);
47
+ }
48
+ const IdsString = gameIds.join(" ");
49
+ await setGameIds(roundInfo, IdsString);
50
+ };
@@ -4,7 +4,7 @@ import { parsePgn, makePgn } from "chessops/pgn";
4
4
  import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
5
5
  import cl from "../utils/colors.js";
6
6
  import { loopChecker, pushPGN, readPGNFromURL } from "../utils/pushTools.js";
7
- const filterPgnByIds = (pgn, filterIds, firstOngoing) => {
7
+ const filterPgnByIds = (pgn, filterIds) => {
8
8
  const parsed = parsePgn(pgn);
9
9
  let filteredGames = parsed.filter((game) => {
10
10
  const whiteFideId = game.headers.get("WhiteFideId");
@@ -26,21 +26,10 @@ const filterPgnByIds = (pgn, filterIds, firstOngoing) => {
26
26
  filterIds.includes(parseInt(blackFideId || "", 10)));
27
27
  }
28
28
  });
29
- if (firstOngoing) {
30
- const ongoingGames = filteredGames.filter((game) => {
31
- const result = game.headers.get("Result");
32
- return !result || result === "*";
33
- });
34
- const gamesFinished = filteredGames.filter((game) => {
35
- const result = game.headers.get("Result");
36
- return result && result !== "*";
37
- });
38
- filteredGames = [...ongoingGames, ...gamesFinished];
39
- }
40
29
  return filteredGames.map((game) => makePgn(game)).join("\n\n");
41
30
  };
42
31
  let lastPGN = "";
43
- const loop = async (roundInfo, pgnPath, loopTimer, filterIds, firstOngoing) => {
32
+ const loop = async (roundInfo, pgnPath, loopTimer, filterIds) => {
44
33
  while (true) {
45
34
  const pgnContent = await readPGNFromURL(pgnPath);
46
35
  if (!pgnContent) {
@@ -48,7 +37,7 @@ const loop = async (roundInfo, pgnPath, loopTimer, filterIds, firstOngoing) => {
48
37
  await sleep(loopTimer * 1000);
49
38
  continue;
50
39
  }
51
- const filteredPgn = filterPgnByIds(pgnContent, filterIds, firstOngoing);
40
+ const filteredPgn = filterPgnByIds(pgnContent, filterIds);
52
41
  if (filteredPgn && filteredPgn !== lastPGN) {
53
42
  await pushPGN(roundInfo, filteredPgn);
54
43
  lastPGN = filteredPgn;
@@ -72,12 +61,11 @@ export const pushFilterIDCommand = async (args) => {
72
61
  console.error(cl.red("Round not found."));
73
62
  exit(1);
74
63
  }
75
- const firstOngoing = args.includes("--firstOngoing");
76
64
  const loopTimer = loopChecker(args);
77
65
  if (loopTimer) {
78
66
  console.log(cl.green(`Starting loop to push PGN every ${cl.whiteBold(loopTimer.toString())} seconds...`));
79
67
  console.log(cl.blue("Press Ctrl+C to stop."));
80
- await loop(roundInfo, pgnPath, loopTimer, filterIds, firstOngoing);
68
+ await loop(roundInfo, pgnPath, loopTimer, filterIds);
81
69
  }
82
70
  else {
83
71
  const pgnContent = await readPGNFromURL(pgnPath);
@@ -85,7 +73,7 @@ export const pushFilterIDCommand = async (args) => {
85
73
  console.error(cl.red(`Failed to read PGN content.`));
86
74
  exit(1);
87
75
  }
88
- const filteredPgn = filterPgnByIds(pgnContent, filterIds, firstOngoing);
76
+ const filteredPgn = filterPgnByIds(pgnContent, filterIds);
89
77
  if (filteredPgn)
90
78
  await pushPGN(roundInfo, filteredPgn);
91
79
  }
@@ -16,9 +16,11 @@ import { pushCommand } from "../cmd/push.js";
16
16
  import { pushFilterIDCommand } from "../cmd/pushFilterID.js";
17
17
  import { loginCommand } from "../cmd/login.js";
18
18
  import { getStoredCredentials } from "./credentials.js";
19
+ import { convertNamesToIDCommand } from "../cmd/convertNamesToID.js";
19
20
  const getToken = () => {
20
21
  const stored = getStoredCredentials();
21
- return stored?.lichessToken;
22
+ const envToken = process.env.LICHESS_TOKEN;
23
+ return envToken || stored?.lichessToken;
22
24
  };
23
25
  const getDomain = () => {
24
26
  const stored = getStoredCredentials();
@@ -44,6 +46,7 @@ export var Command;
44
46
  Command["Score"] = "score";
45
47
  Command["Push"] = "push";
46
48
  Command["PushFilterID"] = "pushFilterID";
49
+ Command["ConvertNamesToID"] = "convertNamesToID";
47
50
  })(Command || (Command = {}));
48
51
  export const commands = new Map([
49
52
  [Command.Login, loginCommand],
@@ -57,6 +60,7 @@ export const commands = new Map([
57
60
  [Command.Score, scoreCommand],
58
61
  [Command.Push, pushCommand],
59
62
  [Command.PushFilterID, pushFilterIDCommand],
63
+ [Command.ConvertNamesToID, convertNamesToIDCommand],
60
64
  ]);
61
65
  export const client = createClient({
62
66
  baseUrl: LICHESS_DOMAIN,
@@ -76,12 +76,16 @@ const helpPush = [
76
76
  ` --loop <intervalInSeconds> ${cl.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
77
77
  ].join("\n");
78
78
  const helpPushFilterID = [
79
- ` ${cl.underItalic("pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>] [--firstOngoing]")}`,
79
+ ` ${cl.underItalic("pushFilterID <roundId> <PGNFromPathOrUrl> <FideIds...> [--loop <intervalInSeconds>]")}`,
80
80
  ` ${cl.gray("Upload a PGN file from a local path or URL to the specified broadcast round, filtering games by FIDE ID.")}`,
81
81
  ` ${cl.bold("Note:")} ${cl.gray("The PGN file must be accessible from the provided path or URL.")}`,
82
82
  ` ${cl.bold("Options:")}`,
83
83
  ` --loop <intervalInSeconds> ${cl.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
84
- ` --firstOngoing ${cl.gray("Push the first ongoing games in each round.")}`,
84
+ ].join("\n");
85
+ const helpConvertNamesToID = [
86
+ ` ${cl.underItalic("convertNamesToID <roundId>")}`,
87
+ ` ${cl.gray("Convert Lichess Usernames source to Lichess Game IDs source for the specified broadcast round.")}`,
88
+ ` ${cl.bold("Note:")} ${cl.gray("This command will fetch the source PGN, extract Lichess Game IDs from the PGN headers, and update the broadcast round to use these game IDs as the source.")}`,
85
89
  ].join("\n");
86
90
  const msg = [
87
91
  `${cl.boldYellow("Usage:")} ${cl.underItalic("<command> [options]")}`,
@@ -110,6 +114,8 @@ const msg = [
110
114
  ``,
111
115
  helpPushFilterID,
112
116
  ``,
117
+ helpConvertNamesToID,
118
+ ``,
113
119
  ``,
114
120
  `${cl.boldYellow("Examples:")}`,
115
121
  ` ${cl.gray("# Login with your Lichess token (interactive)")}`,
@@ -138,6 +144,8 @@ const msg = [
138
144
  ` $ ${cl.underItalic("push")} ${cl.italic("round456 /path/to/localfile.pgn --loop 60")}`,
139
145
  ` ${cl.gray("# Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds")}`,
140
146
  ` $ ${cl.underItalic("pushFilterID")} ${cl.italic("round456 https://example.com/games.pgn 12345 67890 --loop 120")}`,
147
+ ` ${cl.gray("# Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round")}`,
148
+ ` $ ${cl.underItalic("convertNamesToID")} ${cl.italic("round456")}`,
141
149
  ];
142
150
  export const showHelp = (cmd) => {
143
151
  const ranges = {
@@ -152,6 +160,7 @@ export const showHelp = (cmd) => {
152
160
  [Command.Score]: helpScore,
153
161
  [Command.Push]: helpPush,
154
162
  [Command.PushFilterID]: helpPushFilterID,
163
+ [Command.ConvertNamesToID]: helpConvertNamesToID,
155
164
  };
156
165
  const range = cmd ? ranges[cmd] : undefined;
157
166
  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.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "CLI to help with broadcast maintenance on Lichess",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [
@@ -16,7 +16,7 @@
16
16
  "@types/node": "~24.10.4",
17
17
  "chessops": "^0.15.0",
18
18
  "ms": "3.0.0-canary.202508261828",
19
- "openapi-fetch": "^0.15.0"
19
+ "openapi-fetch": "^0.17.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "prettier": "3.8.1",