libroadcast-cli 2.8.0 → 2.10.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
@@ -86,6 +86,16 @@ Commands:
86
86
  Convert Lichess Usernames source to Lichess Game IDs source for the specified broadcast round.
87
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.
88
88
 
89
+ pushReorder <roundId> <PGNFromPathOrUrl> [--loop <intervalInSeconds>]
90
+ Upload a PGN file from a local path or URL to the specified broadcast round, reordering games by round number.
91
+ Note: The PGN file must be accessible from the provided path or URL. Games will be reordered based on the 'Round' header in the PGN.
92
+ Options:
93
+ --loop <intervalInSeconds> Continuously push the PGN file at the specified interval in seconds.
94
+
95
+ bulkIDsMulti <bulkID> <broadcastRoundIds...>
96
+ Sets Lichess Game IDs for multiple broadcast rounds using a Bulk Pairing ID.
97
+ The command will fetch game IDs from the specified Bulk Pairing and distribute them across the provided broadcast round IDs.
98
+
89
99
 
90
100
  Examples:
91
101
  # Login with your Lichess token (interactive)
@@ -116,6 +126,8 @@ Examples:
116
126
  $ pushFilterID round456 https://example.com/games.pgn 12345 67890 --loop 120
117
127
  # Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round
118
128
  $ convertNamesToID round456
129
+ # Set Lichess Game IDs from Bulk Parings for multiple rounds (roundId1, roundId2, roundId3) in a broadcast
130
+ $ bulkIDsMulti bulk123 roundId1 roundId2 roundId3
119
131
  ```
120
132
 
121
133
  ### Test Docker Build Locally
@@ -0,0 +1,58 @@
1
+ import { exit } from "node:process";
2
+ import { client, msgCommonErrorHelp, handleApiResponse, checkTokenScopes, sleep, } from "../utils/commandHandler.js";
3
+ import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
4
+ import cl from "../utils/colors.js";
5
+ const setLichessGames = (round, games) => handleApiResponse(client.POST("/broadcast/round/{broadcastRoundId}/edit", {
6
+ params: {
7
+ path: { broadcastRoundId: round.id },
8
+ query: { patch: 1 },
9
+ },
10
+ body: {
11
+ syncSource: "ids",
12
+ syncIds: games,
13
+ },
14
+ }), `Successfully set games for round ${cl.whiteBold(round.id)} to ${cl.whiteBold(games)}.`, `Error setting games for round ${cl.whiteBold(round.id)}`);
15
+ const getBulkIds = (bulkID) => client
16
+ .GET("/api/bulk-pairing/{id}", {
17
+ params: { path: { id: bulkID } },
18
+ })
19
+ .then((response) => {
20
+ const data = response.data;
21
+ let ids = data?.games
22
+ .map((game) => game.id)
23
+ .filter((id) => typeof id === "string") || [];
24
+ return ids;
25
+ })
26
+ .catch((error) => {
27
+ console.error(cl.red(`Error fetching bulk pairing data: ${error.message}`));
28
+ return [];
29
+ });
30
+ const splitIdsIntoGroups = (broadcastsIds, gameIds) => gameIds.reduce((groups, id, index) => {
31
+ groups[index % broadcastsIds.length].push(id);
32
+ return groups;
33
+ }, broadcastsIds.map(() => []));
34
+ export const bulkIDsMultiCommand = async (args) => {
35
+ await checkTokenScopes();
36
+ const bulkID = args.shift();
37
+ const broadcastsIds = args;
38
+ if (!bulkID || !broadcastsIds) {
39
+ msgCommonErrorHelp("Broadcast ID and rounds IDs are required.");
40
+ exit(1);
41
+ }
42
+ const gameIds = await getBulkIds(bulkID);
43
+ if (gameIds.length === 0) {
44
+ console.error(cl.red(`No game IDs found for bulk ID ${cl.whiteBold(bulkID)}.`));
45
+ exit(1);
46
+ }
47
+ const groupedIds = splitIdsIntoGroups(broadcastsIds, gameIds);
48
+ for (let [index, group] of groupedIds.entries()) {
49
+ const roundId = broadcastsIds[index];
50
+ const round = await getBroadcastRound(roundId);
51
+ if (!round) {
52
+ console.error(cl.red(`Broadcast round with ID ${cl.whiteBold(roundId)} not found or has no rounds.`));
53
+ continue;
54
+ }
55
+ await setLichessGames(round, group.join(" "));
56
+ await sleep(500);
57
+ }
58
+ };
@@ -0,0 +1,61 @@
1
+ import { exit } from "node:process";
2
+ import { msgCommonErrorHelp, sleep, checkTokenScopes, } from "../utils/commandHandler.js";
3
+ import { parsePgn, makePgn } from "chessops/pgn";
4
+ import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
5
+ import cl from "../utils/colors.js";
6
+ import { loopChecker, pushPGN, readPGNFromURL } from "../utils/pushTools.js";
7
+ const sortPgn = (pgn) => {
8
+ const parsed = parsePgn(pgn);
9
+ let sortedGames = parsed.sort((a, b) => {
10
+ const roundA = parseInt(a.headers.get("Round")?.split(".")[1] || "0", 10);
11
+ const roundB = parseInt(b.headers.get("Round")?.split(".")[1] || "0", 10);
12
+ return roundA - roundB;
13
+ });
14
+ return sortedGames.map((game) => makePgn(game)).join("\n\n");
15
+ };
16
+ let lastPGN = "";
17
+ const loop = async (roundInfo, pgnPath, loopTimer) => {
18
+ while (true) {
19
+ const pgnContent = await readPGNFromURL(pgnPath);
20
+ if (!pgnContent) {
21
+ console.error(cl.red(`Failed to read PGN content. Retrying in ${loopTimer} seconds...`));
22
+ await sleep(loopTimer * 1000);
23
+ continue;
24
+ }
25
+ const filteredPgn = sortPgn(pgnContent);
26
+ if (filteredPgn && filteredPgn !== lastPGN) {
27
+ await pushPGN(roundInfo, filteredPgn);
28
+ lastPGN = filteredPgn;
29
+ }
30
+ await sleep(loopTimer * 1000);
31
+ }
32
+ };
33
+ export const pushReorderCommand = async (args) => {
34
+ await checkTokenScopes();
35
+ const [roundId, pgnPath] = args.slice(0, 2);
36
+ if (!roundId || !pgnPath) {
37
+ msgCommonErrorHelp("Round ID and PGN path are required.");
38
+ exit(1);
39
+ }
40
+ const roundInfo = await getBroadcastRound(roundId);
41
+ if (!roundInfo) {
42
+ console.error(cl.red("Round not found."));
43
+ exit(1);
44
+ }
45
+ const loopTimer = loopChecker(args);
46
+ if (loopTimer) {
47
+ console.log(cl.green(`Starting loop to push reordered PGN every ${cl.whiteBold(loopTimer.toString())} seconds...`));
48
+ console.log(cl.blue("Press Ctrl+C to stop."));
49
+ await loop(roundInfo, pgnPath, loopTimer);
50
+ }
51
+ else {
52
+ const pgnContent = await readPGNFromURL(pgnPath);
53
+ if (!pgnContent) {
54
+ console.error(cl.red(`Failed to read PGN content.`));
55
+ exit(1);
56
+ }
57
+ const filteredPgn = sortPgn(pgnContent);
58
+ if (filteredPgn)
59
+ await pushPGN(roundInfo, filteredPgn);
60
+ }
61
+ };
@@ -17,6 +17,8 @@ import { pushFilterIDCommand } from "../cmd/pushFilterID.js";
17
17
  import { loginCommand } from "../cmd/login.js";
18
18
  import { getStoredCredentials } from "./credentials.js";
19
19
  import { convertNamesToIDCommand } from "../cmd/convertNamesToID.js";
20
+ import { pushReorderCommand } from "../cmd/pushReorder.js";
21
+ import { bulkIDsMultiCommand } from "../cmd/bulkIDsMulti.js";
20
22
  const getToken = () => {
21
23
  const stored = getStoredCredentials();
22
24
  const envToken = process.env.LICHESS_TOKEN;
@@ -47,6 +49,8 @@ export var Command;
47
49
  Command["Push"] = "push";
48
50
  Command["PushFilterID"] = "pushFilterID";
49
51
  Command["ConvertNamesToID"] = "convertNamesToID";
52
+ Command["PushReorder"] = "pushReorder";
53
+ Command["BulkIDsMulti"] = "bulkIDsMulti";
50
54
  })(Command || (Command = {}));
51
55
  export const commands = new Map([
52
56
  [Command.Login, loginCommand],
@@ -61,6 +65,8 @@ export const commands = new Map([
61
65
  [Command.Push, pushCommand],
62
66
  [Command.PushFilterID, pushFilterIDCommand],
63
67
  [Command.ConvertNamesToID, convertNamesToIDCommand],
68
+ [Command.PushReorder, pushReorderCommand],
69
+ [Command.BulkIDsMulti, bulkIDsMultiCommand],
64
70
  ]);
65
71
  export const client = createClient({
66
72
  baseUrl: LICHESS_DOMAIN,
@@ -87,6 +87,18 @@ const helpConvertNamesToID = [
87
87
  ` ${cl.gray("Convert Lichess Usernames source to Lichess Game IDs source for the specified broadcast round.")}`,
88
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.")}`,
89
89
  ].join("\n");
90
+ const helpPushReorder = [
91
+ ` ${cl.underItalic("pushReorder <roundId> <PGNFromPathOrUrl> [--loop <intervalInSeconds>]")}`,
92
+ ` ${cl.gray("Upload a PGN file from a local path or URL to the specified broadcast round, reordering games by round number.")}`,
93
+ ` ${cl.bold("Note:")} ${cl.gray("The PGN file must be accessible from the provided path or URL. Games will be reordered based on the 'Round' header in the PGN.")}`,
94
+ ` ${cl.bold("Options:")}`,
95
+ ` --loop <intervalInSeconds> ${cl.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
96
+ ].join("\n");
97
+ const helpBulkIDsMulti = [
98
+ ` ${cl.underItalic("bulkIDsMulti <bulkID> <broadcastRoundIds...>")}`,
99
+ ` ${cl.gray("Sets Lichess Game IDs for multiple broadcast rounds using a Bulk Pairing ID.")}`,
100
+ ` ${cl.gray("The command will fetch game IDs from the specified Bulk Pairing and distribute them across the provided broadcast round IDs.")}`,
101
+ ].join("\n");
90
102
  const msg = [
91
103
  `${cl.boldYellow("Usage:")} ${cl.underItalic("<command> [options]")}`,
92
104
  ``,
@@ -116,6 +128,10 @@ const msg = [
116
128
  ``,
117
129
  helpConvertNamesToID,
118
130
  ``,
131
+ helpPushReorder,
132
+ ``,
133
+ helpBulkIDsMulti,
134
+ ``,
119
135
  ``,
120
136
  `${cl.boldYellow("Examples:")}`,
121
137
  ` ${cl.gray("# Login with your Lichess token (interactive)")}`,
@@ -146,6 +162,8 @@ const msg = [
146
162
  ` $ ${cl.underItalic("pushFilterID")} ${cl.italic("round456 https://example.com/games.pgn 12345 67890 --loop 120")}`,
147
163
  ` ${cl.gray("# Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round")}`,
148
164
  ` $ ${cl.underItalic("convertNamesToID")} ${cl.italic("round456")}`,
165
+ ` ${cl.gray("# Set Lichess Game IDs from Bulk Parings for multiple rounds (roundId1, roundId2, roundId3) in a broadcast")}`,
166
+ ` $ ${cl.underItalic("bulkIDsMulti")} ${cl.italic("bulk123 roundId1 roundId2 roundId3")}`,
149
167
  ];
150
168
  export const showHelp = (cmd) => {
151
169
  const ranges = {
@@ -161,6 +179,8 @@ export const showHelp = (cmd) => {
161
179
  [Command.Push]: helpPush,
162
180
  [Command.PushFilterID]: helpPushFilterID,
163
181
  [Command.ConvertNamesToID]: helpConvertNamesToID,
182
+ [Command.PushReorder]: helpPushReorder,
183
+ [Command.BulkIDsMulti]: helpBulkIDsMulti,
164
184
  };
165
185
  const range = cmd ? ranges[cmd] : undefined;
166
186
  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.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "CLI to help with broadcast maintenance on Lichess",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [
@@ -13,7 +13,7 @@
13
13
  "type": "module",
14
14
  "dependencies": {
15
15
  "@lichess-org/types": "^2.0.109",
16
- "@types/node": "~24.10.4",
16
+ "@types/node": "~24.11.0",
17
17
  "chessops": "^0.15.0",
18
18
  "ms": "3.0.0-canary.202508261828",
19
19
  "openapi-fetch": "^0.17.0"