libroadcast-cli 2.7.0 → 2.9.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 +12 -2
- package/dist/cmd/convertNamesToID.js +50 -0
- package/dist/cmd/pushReorder.js +61 -0
- package/dist/utils/commandHandler.js +6 -0
- package/dist/utils/help.js +19 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -76,13 +76,21 @@ 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>]
|
|
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.
|
|
85
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.
|
|
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.
|
|
86
94
|
|
|
87
95
|
Examples:
|
|
88
96
|
# Login with your Lichess token (interactive)
|
|
@@ -111,6 +119,8 @@ Examples:
|
|
|
111
119
|
$ push round456 /path/to/localfile.pgn --loop 60
|
|
112
120
|
# Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds
|
|
113
121
|
$ pushFilterID round456 https://example.com/games.pgn 12345 67890 --loop 120
|
|
122
|
+
# Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round
|
|
123
|
+
$ convertNamesToID round456
|
|
114
124
|
```
|
|
115
125
|
|
|
116
126
|
### 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
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -16,6 +16,8 @@ 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";
|
|
20
|
+
import { pushReorderCommand } from "../cmd/pushReorder.js";
|
|
19
21
|
const getToken = () => {
|
|
20
22
|
const stored = getStoredCredentials();
|
|
21
23
|
const envToken = process.env.LICHESS_TOKEN;
|
|
@@ -45,6 +47,8 @@ export var Command;
|
|
|
45
47
|
Command["Score"] = "score";
|
|
46
48
|
Command["Push"] = "push";
|
|
47
49
|
Command["PushFilterID"] = "pushFilterID";
|
|
50
|
+
Command["ConvertNamesToID"] = "convertNamesToID";
|
|
51
|
+
Command["PushReorder"] = "pushReorder";
|
|
48
52
|
})(Command || (Command = {}));
|
|
49
53
|
export const commands = new Map([
|
|
50
54
|
[Command.Login, loginCommand],
|
|
@@ -58,6 +62,8 @@ export const commands = new Map([
|
|
|
58
62
|
[Command.Score, scoreCommand],
|
|
59
63
|
[Command.Push, pushCommand],
|
|
60
64
|
[Command.PushFilterID, pushFilterIDCommand],
|
|
65
|
+
[Command.ConvertNamesToID, convertNamesToIDCommand],
|
|
66
|
+
[Command.PushReorder, pushReorderCommand],
|
|
61
67
|
]);
|
|
62
68
|
export const client = createClient({
|
|
63
69
|
baseUrl: LICHESS_DOMAIN,
|
package/dist/utils/help.js
CHANGED
|
@@ -82,6 +82,18 @@ const helpPushFilterID = [
|
|
|
82
82
|
` ${cl.bold("Options:")}`,
|
|
83
83
|
` --loop <intervalInSeconds> ${cl.gray("Continuously push the PGN file at the specified interval in seconds.")}`,
|
|
84
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.")}`,
|
|
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");
|
|
85
97
|
const msg = [
|
|
86
98
|
`${cl.boldYellow("Usage:")} ${cl.underItalic("<command> [options]")}`,
|
|
87
99
|
``,
|
|
@@ -109,6 +121,9 @@ const msg = [
|
|
|
109
121
|
``,
|
|
110
122
|
helpPushFilterID,
|
|
111
123
|
``,
|
|
124
|
+
helpConvertNamesToID,
|
|
125
|
+
``,
|
|
126
|
+
helpPushReorder,
|
|
112
127
|
``,
|
|
113
128
|
`${cl.boldYellow("Examples:")}`,
|
|
114
129
|
` ${cl.gray("# Login with your Lichess token (interactive)")}`,
|
|
@@ -137,6 +152,8 @@ const msg = [
|
|
|
137
152
|
` $ ${cl.underItalic("push")} ${cl.italic("round456 /path/to/localfile.pgn --loop 60")}`,
|
|
138
153
|
` ${cl.gray("# Push a PGN file from URL filtering by FIDE IDs in loop mode every 120 seconds")}`,
|
|
139
154
|
` $ ${cl.underItalic("pushFilterID")} ${cl.italic("round456 https://example.com/games.pgn 12345 67890 --loop 120")}`,
|
|
155
|
+
` ${cl.gray("# Convert Lichess Usernames source to Lichess Game IDs source for a broadcast round")}`,
|
|
156
|
+
` $ ${cl.underItalic("convertNamesToID")} ${cl.italic("round456")}`,
|
|
140
157
|
];
|
|
141
158
|
export const showHelp = (cmd) => {
|
|
142
159
|
const ranges = {
|
|
@@ -151,6 +168,8 @@ export const showHelp = (cmd) => {
|
|
|
151
168
|
[Command.Score]: helpScore,
|
|
152
169
|
[Command.Push]: helpPush,
|
|
153
170
|
[Command.PushFilterID]: helpPushFilterID,
|
|
171
|
+
[Command.ConvertNamesToID]: helpConvertNamesToID,
|
|
172
|
+
[Command.PushReorder]: helpPushReorder,
|
|
154
173
|
};
|
|
155
174
|
const range = cmd ? ranges[cmd] : undefined;
|
|
156
175
|
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
|
+
"version": "2.9.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.
|
|
19
|
+
"openapi-fetch": "^0.17.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"prettier": "3.8.1",
|