libroadcast-cli 2.3.0 → 2.4.1
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 +8 -0
- package/dist/cmd/delay.js +19 -26
- package/dist/cmd/fixSchedule.js +22 -29
- package/dist/cmd/login.js +30 -37
- package/dist/cmd/period.js +17 -24
- package/dist/cmd/push.js +26 -33
- package/dist/cmd/pushFilterID.js +134 -0
- package/dist/cmd/score.js +19 -26
- package/dist/cmd/setLichessGames.js +13 -20
- package/dist/cmd/setPGN.js +19 -26
- package/dist/cmd/setPGNMulti.js +23 -30
- package/dist/cmd/startsPrevious.js +14 -21
- package/dist/index.js +23 -28
- package/dist/utils/colors.js +12 -14
- package/dist/utils/commandHandler.js +56 -63
- package/dist/utils/credentials.js +16 -23
- package/dist/utils/getInfoBroadcast.js +6 -14
- package/dist/utils/help.js +108 -104
- package/package.json +4 -3
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
|
```
|
package/dist/cmd/delay.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.delayCommand = void 0;
|
|
7
|
-
const node_process_1 = require("node:process");
|
|
8
|
-
const commandHandler_1 = require("../utils/commandHandler");
|
|
9
|
-
const getInfoBroadcast_1 = require("../utils/getInfoBroadcast");
|
|
10
|
-
const colors_1 = __importDefault(require("../utils/colors"));
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { client, msgCommonErrorHelp, sleep, handleApiResponse, translateRoundsToFix, checkTokenScopes, } from "../utils/commandHandler.js";
|
|
3
|
+
import { getBroadcast } from "../utils/getInfoBroadcast.js";
|
|
4
|
+
import cl from "../utils/colors.js";
|
|
11
5
|
const setDelayRounds = async (rounds, delay, onlyDelay, noDelay, roundsToFix) => {
|
|
12
6
|
let filteredRounds = rounds.filter((_, i) => !roundsToFix?.length || roundsToFix.includes(i + 1));
|
|
13
7
|
if (filteredRounds.length === 0)
|
|
14
8
|
filteredRounds = rounds;
|
|
15
9
|
for (const round of filteredRounds) {
|
|
16
|
-
await
|
|
10
|
+
await handleApiResponse(client.POST("/broadcast/round/{broadcastRoundId}/edit", {
|
|
17
11
|
params: {
|
|
18
12
|
path: { broadcastRoundId: round.id },
|
|
19
13
|
query: { patch: 1 },
|
|
@@ -24,39 +18,38 @@ const setDelayRounds = async (rounds, delay, onlyDelay, noDelay, roundsToFix) =>
|
|
|
24
18
|
? round.startsAt + delay * 1000
|
|
25
19
|
: undefined,
|
|
26
20
|
},
|
|
27
|
-
}), `Successfully set delay for round ${
|
|
28
|
-
await
|
|
21
|
+
}), `Successfully set delay for round ${cl.whiteBold(round.id)} to ${cl.whiteBold(delay.toString())} seconds.`, `Error setting delay for round ${cl.whiteBold(round.id)}`);
|
|
22
|
+
await sleep(200);
|
|
29
23
|
}
|
|
30
24
|
};
|
|
31
|
-
const delayCommand = async (args) => {
|
|
32
|
-
await
|
|
25
|
+
export const delayCommand = async (args) => {
|
|
26
|
+
await checkTokenScopes();
|
|
33
27
|
const [broadcastId, delay] = args.slice(0, 2);
|
|
34
28
|
if (!broadcastId || !delay) {
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
msgCommonErrorHelp("Broadcast ID and delay are required.");
|
|
30
|
+
exit(1);
|
|
37
31
|
}
|
|
38
32
|
const delayNum = parseInt(delay, 10);
|
|
39
33
|
if (isNaN(delayNum) || delayNum < 0 || delayNum > 3600) {
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
msgCommonErrorHelp("Delay must be a number between 0 and 3600 seconds.");
|
|
35
|
+
exit(1);
|
|
42
36
|
}
|
|
43
37
|
const onlyDelay = args.includes("--onlyDelay");
|
|
44
38
|
const noDelay = args.includes("--noDelay");
|
|
45
39
|
if (onlyDelay && noDelay) {
|
|
46
|
-
console.error(
|
|
47
|
-
|
|
40
|
+
console.error(cl.red("Cannot use --onlyDelay and --noDelay together."));
|
|
41
|
+
exit(1);
|
|
48
42
|
}
|
|
49
43
|
const roundsArgIndex = args.findIndex((arg) => arg === "--rounds");
|
|
50
44
|
let roundsToFix = undefined;
|
|
51
45
|
if (roundsArgIndex !== -1 && roundsArgIndex + 1 < args.length) {
|
|
52
46
|
const roundsArg = args[roundsArgIndex + 1];
|
|
53
|
-
roundsToFix = roundsArg ?
|
|
47
|
+
roundsToFix = roundsArg ? translateRoundsToFix(roundsArg) : undefined;
|
|
54
48
|
}
|
|
55
|
-
const broadcast = await
|
|
49
|
+
const broadcast = await getBroadcast(broadcastId);
|
|
56
50
|
if (!broadcast?.rounds || broadcast.rounds.length === 0) {
|
|
57
|
-
console.error(
|
|
58
|
-
|
|
51
|
+
console.error(cl.red("No rounds found for the specified broadcast."));
|
|
52
|
+
exit(1);
|
|
59
53
|
}
|
|
60
54
|
await setDelayRounds(broadcast.rounds, delayNum, onlyDelay, noDelay, roundsToFix);
|
|
61
55
|
};
|
|
62
|
-
exports.delayCommand = delayCommand;
|
package/dist/cmd/fixSchedule.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.fixScheduleCommand = void 0;
|
|
7
|
-
const node_process_1 = require("node:process");
|
|
8
|
-
const commandHandler_1 = require("../utils/commandHandler");
|
|
9
|
-
const getInfoBroadcast_1 = require("../utils/getInfoBroadcast");
|
|
10
|
-
const colors_1 = __importDefault(require("../utils/colors"));
|
|
11
|
-
const ms_1 = require("ms");
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { client, msgCommonErrorHelp, sleep, handleApiResponse, translateRoundsToFix, checkTokenScopes, } from "../utils/commandHandler.js";
|
|
3
|
+
import { getBroadcast } from "../utils/getInfoBroadcast.js";
|
|
4
|
+
import cl from "../utils/colors.js";
|
|
5
|
+
import { parse as ms } from "ms";
|
|
12
6
|
const fixScheduleRounds = async (rounds, timeDiff, roundsToFix) => {
|
|
13
7
|
rounds = rounds
|
|
14
8
|
.filter((_, i) => !roundsToFix?.length || roundsToFix.includes(i + 1))
|
|
15
9
|
.filter((el) => el.startsAt !== undefined);
|
|
16
10
|
if (rounds.length === 0) {
|
|
17
|
-
console.error(
|
|
18
|
-
|
|
11
|
+
console.error(cl.red("No rounds to fix after applying filters."));
|
|
12
|
+
exit(1);
|
|
19
13
|
}
|
|
20
14
|
for (const round of rounds) {
|
|
21
|
-
await
|
|
15
|
+
await handleApiResponse(client.POST("/broadcast/round/{broadcastRoundId}/edit", {
|
|
22
16
|
params: {
|
|
23
17
|
path: { broadcastRoundId: round.id },
|
|
24
18
|
query: { patch: 1 },
|
|
@@ -26,34 +20,33 @@ const fixScheduleRounds = async (rounds, timeDiff, roundsToFix) => {
|
|
|
26
20
|
body: {
|
|
27
21
|
startsAt: round.startsAt + timeDiff,
|
|
28
22
|
},
|
|
29
|
-
}), `Successfully fixed schedule for round ${
|
|
30
|
-
await
|
|
23
|
+
}), `Successfully fixed schedule for round ${cl.whiteBold(round.id)}.`, `Error fixing schedule for round ${cl.whiteBold(round.id)}`);
|
|
24
|
+
await sleep(200);
|
|
31
25
|
}
|
|
32
26
|
};
|
|
33
|
-
const fixScheduleCommand = async (args) => {
|
|
34
|
-
await
|
|
27
|
+
export const fixScheduleCommand = async (args) => {
|
|
28
|
+
await checkTokenScopes();
|
|
35
29
|
const [broadcastId, timeDiffStr] = args.slice(0, 2);
|
|
36
30
|
if (!broadcastId || !timeDiffStr) {
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
msgCommonErrorHelp("Broadcast ID and time difference are required.");
|
|
32
|
+
exit(1);
|
|
39
33
|
}
|
|
40
|
-
const timeDiff = (
|
|
34
|
+
const timeDiff = ms(timeDiffStr);
|
|
41
35
|
if (isNaN(timeDiff)) {
|
|
42
|
-
console.error(
|
|
43
|
-
|
|
36
|
+
console.error(cl.red("Error: Time difference must be a valid duration string (e.g., '1h', '30m', '15s')."));
|
|
37
|
+
exit(1);
|
|
44
38
|
}
|
|
45
|
-
console.log(`Applying time difference of ${
|
|
39
|
+
console.log(`Applying time difference of ${cl.whiteBold(timeDiffStr)} (${cl.whiteBold(timeDiff.toString())} ms) to broadcast ${cl.whiteBold(broadcastId)}.`);
|
|
46
40
|
const roundsArgIndex = args.findIndex((arg) => arg === "--rounds");
|
|
47
41
|
let roundsToFix = undefined;
|
|
48
42
|
if (roundsArgIndex !== -1 && roundsArgIndex + 1 < args.length) {
|
|
49
43
|
const roundsArg = args[roundsArgIndex + 1];
|
|
50
|
-
roundsToFix = roundsArg ?
|
|
44
|
+
roundsToFix = roundsArg ? translateRoundsToFix(roundsArg) : undefined;
|
|
51
45
|
}
|
|
52
|
-
const broadcast = await
|
|
46
|
+
const broadcast = await getBroadcast(broadcastId);
|
|
53
47
|
if (!broadcast?.rounds || broadcast.rounds.length === 0) {
|
|
54
|
-
console.error(
|
|
55
|
-
|
|
48
|
+
console.error(cl.red(`Broadcast with ID ${cl.whiteBold(broadcastId)} not found or has no rounds.`));
|
|
49
|
+
exit(1);
|
|
56
50
|
}
|
|
57
51
|
await fixScheduleRounds(broadcast.rounds, timeDiff, roundsToFix);
|
|
58
52
|
};
|
|
59
|
-
exports.fixScheduleCommand = fixScheduleCommand;
|
package/dist/cmd/login.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.loginCommand = void 0;
|
|
7
|
-
const node_process_1 = require("node:process");
|
|
8
|
-
const node_readline_1 = require("node:readline");
|
|
9
|
-
const credentials_1 = require("../utils/credentials");
|
|
10
|
-
const colors_1 = __importDefault(require("../utils/colors"));
|
|
11
|
-
const loginCommand = async (args) => {
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { saveCredentials, clearCredentials, fetchTokenScopes, } from "../utils/credentials.js";
|
|
4
|
+
import cl from "../utils/colors.js";
|
|
5
|
+
export const loginCommand = async (args) => {
|
|
12
6
|
if (args.includes("--logout") || args.includes("-lo")) {
|
|
13
7
|
try {
|
|
14
|
-
|
|
15
|
-
console.log(
|
|
8
|
+
clearCredentials();
|
|
9
|
+
console.log(cl.green("✓ Credentials cleared successfully. You are now logged out."));
|
|
16
10
|
return;
|
|
17
11
|
}
|
|
18
12
|
catch (error) {
|
|
19
|
-
console.error(
|
|
20
|
-
|
|
13
|
+
console.error(cl.red("Error clearing credentials:"), error);
|
|
14
|
+
exit(1);
|
|
21
15
|
}
|
|
22
16
|
}
|
|
23
17
|
try {
|
|
@@ -25,7 +19,7 @@ const loginCommand = async (args) => {
|
|
|
25
19
|
let token = positionalArgs[0];
|
|
26
20
|
let domain = positionalArgs[1] || "https://lichess.org";
|
|
27
21
|
if (!token) {
|
|
28
|
-
const readline =
|
|
22
|
+
const readline = createInterface({
|
|
29
23
|
input: process.stdin,
|
|
30
24
|
output: process.stdout,
|
|
31
25
|
});
|
|
@@ -36,59 +30,58 @@ const loginCommand = async (args) => {
|
|
|
36
30
|
});
|
|
37
31
|
});
|
|
38
32
|
};
|
|
39
|
-
console.log(
|
|
40
|
-
console.log(
|
|
33
|
+
console.log(cl.whiteBold("Lichess Token Login"));
|
|
34
|
+
console.log(cl.blue("Please enter your Lichess token \n" +
|
|
41
35
|
"You can generate one at https://lichess.org/account/oauth/token/create?scopes[]=study:write&scopes[]=study:read&scopes[]=web:mod&description=Broadcast+CLI"));
|
|
42
36
|
console.log("");
|
|
43
|
-
token = await question(
|
|
37
|
+
token = await question(cl.whiteBold("Lichess Token: "));
|
|
44
38
|
if (!token?.trim()) {
|
|
45
|
-
console.error(
|
|
39
|
+
console.error(cl.red("Error: Token cannot be empty."));
|
|
46
40
|
readline.close();
|
|
47
|
-
|
|
41
|
+
exit(1);
|
|
48
42
|
}
|
|
49
43
|
if (!token.startsWith("lip_")) {
|
|
50
|
-
console.error(
|
|
44
|
+
console.error(cl.red("Error: Invalid token format. Token must start with 'lip_'. Please check your token."));
|
|
51
45
|
readline.close();
|
|
52
|
-
|
|
46
|
+
exit(1);
|
|
53
47
|
}
|
|
54
48
|
domain =
|
|
55
|
-
(await question(
|
|
49
|
+
(await question(cl.whiteBold("Lichess Domain (default: https://lichess.org): "))) || "https://lichess.org";
|
|
56
50
|
readline.close();
|
|
57
51
|
}
|
|
58
52
|
else {
|
|
59
53
|
if (!token.startsWith("lip_")) {
|
|
60
|
-
console.error(
|
|
61
|
-
|
|
54
|
+
console.error(cl.red("Error: Invalid token format. Token must start with 'lip_'. Please check your token."));
|
|
55
|
+
exit(1);
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
58
|
domain = domain.replace(/\/$/, "");
|
|
65
59
|
const skipValidation = args.includes("--skip-validation");
|
|
66
60
|
let scopes = [];
|
|
67
61
|
if (!skipValidation) {
|
|
68
|
-
console.log(
|
|
62
|
+
console.log(cl.blue("Validating token and fetching scopes..."));
|
|
69
63
|
try {
|
|
70
|
-
scopes = await
|
|
71
|
-
console.log(
|
|
64
|
+
scopes = await fetchTokenScopes(token, domain);
|
|
65
|
+
console.log(cl.green(`✓ Token valid with scopes: ${scopes.join(", ")}`));
|
|
72
66
|
}
|
|
73
67
|
catch (error) {
|
|
74
|
-
console.error(
|
|
75
|
-
|
|
68
|
+
console.error(cl.red(`Error: Failed to validate token: ${error instanceof Error ? error.message : String(error)}`));
|
|
69
|
+
exit(1);
|
|
76
70
|
}
|
|
77
71
|
}
|
|
78
72
|
else {
|
|
79
|
-
console.log(
|
|
73
|
+
console.log(cl.blue("Skipping token validation..."));
|
|
80
74
|
}
|
|
81
|
-
|
|
75
|
+
saveCredentials({
|
|
82
76
|
lichessToken: token,
|
|
83
77
|
lichessDomain: domain,
|
|
84
78
|
scopes: scopes,
|
|
85
79
|
});
|
|
86
80
|
console.log("");
|
|
87
|
-
console.log(
|
|
81
|
+
console.log(cl.green("✓ Credentials saved successfully! You can now use the CLI without setting environment variables."));
|
|
88
82
|
}
|
|
89
83
|
catch (error) {
|
|
90
|
-
console.error(
|
|
91
|
-
|
|
84
|
+
console.error(cl.red("Error during login:"), error);
|
|
85
|
+
exit(1);
|
|
92
86
|
}
|
|
93
87
|
};
|
|
94
|
-
exports.loginCommand = loginCommand;
|
package/dist/cmd/period.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.periodCommand = void 0;
|
|
7
|
-
const node_process_1 = require("node:process");
|
|
8
|
-
const commandHandler_1 = require("../utils/commandHandler");
|
|
9
|
-
const getInfoBroadcast_1 = require("../utils/getInfoBroadcast");
|
|
10
|
-
const colors_1 = __importDefault(require("../utils/colors"));
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { client, msgCommonErrorHelp, sleep, handleApiResponse, translateRoundsToFix, checkTokenScopes, } from "../utils/commandHandler.js";
|
|
3
|
+
import { getBroadcast } from "../utils/getInfoBroadcast.js";
|
|
4
|
+
import cl from "../utils/colors.js";
|
|
11
5
|
const setPeriodRounds = async (rounds, period, roundsToFix) => {
|
|
12
6
|
let filteredRounds = rounds.filter((_, i) => !roundsToFix?.length || roundsToFix.includes(i + 1));
|
|
13
7
|
if (filteredRounds.length === 0)
|
|
14
8
|
filteredRounds = rounds;
|
|
15
9
|
for (const round of filteredRounds) {
|
|
16
|
-
await
|
|
10
|
+
await handleApiResponse(client.POST("/broadcast/round/{broadcastRoundId}/edit", {
|
|
17
11
|
params: {
|
|
18
12
|
path: { broadcastRoundId: round.id },
|
|
19
13
|
query: { patch: 1 },
|
|
@@ -21,33 +15,32 @@ const setPeriodRounds = async (rounds, period, roundsToFix) => {
|
|
|
21
15
|
body: {
|
|
22
16
|
period: period,
|
|
23
17
|
},
|
|
24
|
-
}), `Successfully set period for round ${
|
|
25
|
-
await
|
|
18
|
+
}), `Successfully set period for round ${cl.whiteBold(round.id)} to ${cl.whiteBold(period.toString())} seconds.`, `Error setting period for round ${cl.whiteBold(round.id)}`);
|
|
19
|
+
await sleep(200);
|
|
26
20
|
}
|
|
27
21
|
};
|
|
28
|
-
const periodCommand = async (args) => {
|
|
29
|
-
await
|
|
22
|
+
export const periodCommand = async (args) => {
|
|
23
|
+
await checkTokenScopes(true);
|
|
30
24
|
const [broadcastId, period] = args.slice(0, 2);
|
|
31
25
|
if (!broadcastId || !period) {
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
msgCommonErrorHelp("Broadcast ID and period are required.");
|
|
27
|
+
exit(1);
|
|
34
28
|
}
|
|
35
29
|
const periodNum = parseInt(period, 10);
|
|
36
30
|
if (isNaN(periodNum) || periodNum < 2 || periodNum > 60) {
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
msgCommonErrorHelp("Period must be a number between 2 and 60 seconds.");
|
|
32
|
+
exit(1);
|
|
39
33
|
}
|
|
40
34
|
const roundsArgIndex = args.findIndex((arg) => arg === "--rounds");
|
|
41
35
|
let roundsToFix = undefined;
|
|
42
36
|
if (roundsArgIndex !== -1 && roundsArgIndex + 1 < args.length) {
|
|
43
37
|
const roundsArg = args[roundsArgIndex + 1];
|
|
44
|
-
roundsToFix = roundsArg ?
|
|
38
|
+
roundsToFix = roundsArg ? translateRoundsToFix(roundsArg) : undefined;
|
|
45
39
|
}
|
|
46
|
-
const broadcast = await
|
|
40
|
+
const broadcast = await getBroadcast(broadcastId);
|
|
47
41
|
if (!broadcast?.rounds || broadcast.rounds.length === 0) {
|
|
48
|
-
console.error(
|
|
49
|
-
|
|
42
|
+
console.error(cl.red("No rounds found for the specified broadcast."));
|
|
43
|
+
exit(1);
|
|
50
44
|
}
|
|
51
45
|
await setPeriodRounds(broadcast.rounds, periodNum, roundsToFix);
|
|
52
46
|
};
|
|
53
|
-
exports.periodCommand = periodCommand;
|
package/dist/cmd/push.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
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 getInfoBroadcast_1 = require("../utils/getInfoBroadcast");
|
|
12
|
-
const colors_1 = __importDefault(require("../utils/colors"));
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { client, msgCommonErrorHelp, sleep, checkTokenScopes, packageJson, } from "../utils/commandHandler.js";
|
|
5
|
+
import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
|
|
6
|
+
import cl from "../utils/colors.js";
|
|
13
7
|
const pushPGN = async (round, pgn) => {
|
|
14
8
|
try {
|
|
15
|
-
const res = await
|
|
9
|
+
const res = await client
|
|
16
10
|
.POST("/api/broadcast/round/{broadcastRoundId}/push", {
|
|
17
11
|
params: {
|
|
18
12
|
path: { broadcastRoundId: round.id },
|
|
@@ -21,7 +15,7 @@ const pushPGN = async (round, pgn) => {
|
|
|
21
15
|
bodySerializer: (body) => body,
|
|
22
16
|
})
|
|
23
17
|
.then((response) => response.data);
|
|
24
|
-
console.log(
|
|
18
|
+
console.log(cl.green(`✓ Successfully pushed PGN for round ${cl.whiteBold(round.id)}.`));
|
|
25
19
|
console.table(res?.games.map((game, i) => {
|
|
26
20
|
return {
|
|
27
21
|
"Game #": i + 1,
|
|
@@ -34,7 +28,7 @@ const pushPGN = async (round, pgn) => {
|
|
|
34
28
|
}));
|
|
35
29
|
}
|
|
36
30
|
catch (error) {
|
|
37
|
-
console.error(
|
|
31
|
+
console.error(cl.red(`Error pushing PGN for round ${cl.whiteBold(round.id)}:`), error);
|
|
38
32
|
}
|
|
39
33
|
};
|
|
40
34
|
const readPGNFromURL = async (pgnURL) => {
|
|
@@ -42,20 +36,20 @@ const readPGNFromURL = async (pgnURL) => {
|
|
|
42
36
|
const response = await fetch(pgnURL, {
|
|
43
37
|
method: "GET",
|
|
44
38
|
headers: {
|
|
45
|
-
"User-Agent":
|
|
39
|
+
"User-Agent": packageJson.name + "/" + packageJson.version,
|
|
46
40
|
},
|
|
47
41
|
});
|
|
48
42
|
if (!response.ok) {
|
|
49
|
-
console.error(
|
|
43
|
+
console.error(cl.red(`Failed to fetch PGN from URL: ${response.statusText}`));
|
|
50
44
|
return undefined;
|
|
51
45
|
}
|
|
52
46
|
const pgnText = await response.text();
|
|
53
47
|
return pgnText;
|
|
54
48
|
}
|
|
55
49
|
else {
|
|
56
|
-
const resolvedPath =
|
|
57
|
-
const stats = await
|
|
58
|
-
console.error(
|
|
50
|
+
const resolvedPath = path.resolve(pgnURL);
|
|
51
|
+
const stats = await readFile(resolvedPath, { encoding: "utf-8" }).catch((err) => {
|
|
52
|
+
console.error(cl.red(`Failed to read PGN file: ${err.message}`));
|
|
59
53
|
return undefined;
|
|
60
54
|
});
|
|
61
55
|
if (!stats)
|
|
@@ -68,20 +62,20 @@ const loop = async (roundInfo, pgnPath, loopTimer) => {
|
|
|
68
62
|
const pgnContent = await readPGNFromURL(pgnPath);
|
|
69
63
|
if (pgnContent)
|
|
70
64
|
await pushPGN(roundInfo, pgnContent);
|
|
71
|
-
await
|
|
65
|
+
await sleep(loopTimer * 1000);
|
|
72
66
|
}
|
|
73
67
|
};
|
|
74
|
-
const pushCommand = async (args) => {
|
|
75
|
-
await
|
|
68
|
+
export const pushCommand = async (args) => {
|
|
69
|
+
await checkTokenScopes();
|
|
76
70
|
const [roundId, pgnPath] = args.slice(0, 2);
|
|
77
71
|
if (!roundId || !pgnPath) {
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
msgCommonErrorHelp("Round ID and PGN are required.");
|
|
73
|
+
exit(1);
|
|
80
74
|
}
|
|
81
|
-
const roundInfo = await
|
|
75
|
+
const roundInfo = await getBroadcastRound(roundId);
|
|
82
76
|
if (!roundInfo) {
|
|
83
|
-
console.error(
|
|
84
|
-
|
|
77
|
+
console.error(cl.red("Round not found."));
|
|
78
|
+
exit(1);
|
|
85
79
|
}
|
|
86
80
|
const loopArgIndex = args.findIndex((arg) => arg === "--loop");
|
|
87
81
|
let loopTimer = undefined;
|
|
@@ -89,13 +83,13 @@ const pushCommand = async (args) => {
|
|
|
89
83
|
const loopTimerStr = args[loopArgIndex + 1];
|
|
90
84
|
loopTimer = parseInt(loopTimerStr, 10);
|
|
91
85
|
if (isNaN(loopTimer) || loopTimer <= 0) {
|
|
92
|
-
console.error(
|
|
93
|
-
|
|
86
|
+
console.error(cl.red("Loop timer must be a positive integer."));
|
|
87
|
+
exit(1);
|
|
94
88
|
}
|
|
95
89
|
}
|
|
96
90
|
if (loopTimer) {
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
91
|
+
console.log(cl.green(`Starting loop to push PGN every ${cl.whiteBold(loopTimer.toString())} seconds...`));
|
|
92
|
+
console.log(cl.blue("Press Ctrl+C to stop."));
|
|
99
93
|
await loop(roundInfo, pgnPath, loopTimer);
|
|
100
94
|
}
|
|
101
95
|
else {
|
|
@@ -104,4 +98,3 @@ const pushCommand = async (args) => {
|
|
|
104
98
|
await pushPGN(roundInfo, pgnContent);
|
|
105
99
|
}
|
|
106
100
|
};
|
|
107
|
-
exports.pushCommand = pushCommand;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { client, msgCommonErrorHelp, sleep, checkTokenScopes, packageJson, } from "../utils/commandHandler.js";
|
|
5
|
+
import { parsePgn, makePgn } from "chessops/pgn";
|
|
6
|
+
import { getBroadcastRound } from "../utils/getInfoBroadcast.js";
|
|
7
|
+
import cl from "../utils/colors.js";
|
|
8
|
+
const pushPGN = async (round, pgn) => {
|
|
9
|
+
try {
|
|
10
|
+
const res = await client
|
|
11
|
+
.POST("/api/broadcast/round/{broadcastRoundId}/push", {
|
|
12
|
+
params: {
|
|
13
|
+
path: { broadcastRoundId: round.id },
|
|
14
|
+
},
|
|
15
|
+
body: pgn,
|
|
16
|
+
bodySerializer: (body) => body,
|
|
17
|
+
})
|
|
18
|
+
.then((response) => response.data);
|
|
19
|
+
console.log(cl.green(`✓ Successfully pushed PGN for round ${cl.whiteBold(round.id)}.`));
|
|
20
|
+
console.table(res?.games.map((game, i) => {
|
|
21
|
+
return {
|
|
22
|
+
"Game #": i + 1,
|
|
23
|
+
"White Player": game.tags["White"] || "Unknown",
|
|
24
|
+
"Black Player": game.tags["Black"] || "Unknown",
|
|
25
|
+
Result: game.tags["Result"] || "Unknown",
|
|
26
|
+
"Ply Count": game.moves || "Unknown",
|
|
27
|
+
Error: game.error || "None",
|
|
28
|
+
};
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(cl.red(`Error pushing PGN for round ${cl.whiteBold(round.id)}:`), error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const filterPgnByIds = (pgn, filterIds) => {
|
|
36
|
+
const parsed = parsePgn(pgn);
|
|
37
|
+
const filteredGames = parsed.filter((game) => {
|
|
38
|
+
const whiteFideId = game.headers.get("WhiteFideId");
|
|
39
|
+
const blackFideId = game.headers.get("BlackFideId");
|
|
40
|
+
if (filterIds.length > 1) {
|
|
41
|
+
for (let i = 0; i < filterIds.length; i += 2) {
|
|
42
|
+
const whiteId = filterIds[i];
|
|
43
|
+
const blackId = filterIds[i + 1];
|
|
44
|
+
if ((whiteFideId === String(whiteId) &&
|
|
45
|
+
blackFideId === String(blackId)) ||
|
|
46
|
+
(whiteFideId === String(blackId) && blackFideId === String(whiteId))) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
return (filterIds.includes(parseInt(whiteFideId || "", 10)) ||
|
|
54
|
+
filterIds.includes(parseInt(blackFideId || "", 10)));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return filteredGames.map((game) => makePgn(game)).join("\n\n");
|
|
58
|
+
};
|
|
59
|
+
const readPGNFromURL = async (pgnURL, filterIds) => {
|
|
60
|
+
if (pgnURL.startsWith("http://") || pgnURL.startsWith("https://")) {
|
|
61
|
+
const response = await fetch(pgnURL, {
|
|
62
|
+
method: "GET",
|
|
63
|
+
headers: {
|
|
64
|
+
"User-Agent": packageJson.name + "/" + packageJson.version,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
console.error(cl.red(`Failed to fetch PGN from URL: ${response.statusText}`));
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const pgnText = await response.text();
|
|
72
|
+
const filteredPgn = filterPgnByIds(pgnText, filterIds);
|
|
73
|
+
if (!filteredPgn) {
|
|
74
|
+
console.error(cl.red(`No games found matching the provided filter IDs.`));
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return filteredPgn;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const resolvedPath = path.resolve(pgnURL);
|
|
81
|
+
const stats = await readFile(resolvedPath, { encoding: "utf-8" }).catch((err) => {
|
|
82
|
+
console.error(cl.red(`Failed to read PGN file: ${err.message}`));
|
|
83
|
+
return undefined;
|
|
84
|
+
});
|
|
85
|
+
if (!stats)
|
|
86
|
+
return undefined;
|
|
87
|
+
return stats.toString();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const loop = async (roundInfo, pgnPath, loopTimer, filterIds) => {
|
|
91
|
+
while (true) {
|
|
92
|
+
const pgnContent = await readPGNFromURL(pgnPath, filterIds);
|
|
93
|
+
if (pgnContent)
|
|
94
|
+
await pushPGN(roundInfo, pgnContent);
|
|
95
|
+
await sleep(loopTimer * 1000);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export const pushFilterIDCommand = async (args) => {
|
|
99
|
+
await checkTokenScopes();
|
|
100
|
+
const [roundId, pgnPath] = args.slice(0, 2);
|
|
101
|
+
const filterIds = args
|
|
102
|
+
.slice(2)
|
|
103
|
+
.filter((arg) => !arg.startsWith("--") && Number.isInteger(parseInt(arg)))
|
|
104
|
+
.map((arg) => parseInt(arg, 10));
|
|
105
|
+
if (!roundId || !pgnPath || filterIds.length === 0) {
|
|
106
|
+
msgCommonErrorHelp("Round ID, PGN, and at least one filter ID are required.");
|
|
107
|
+
exit(1);
|
|
108
|
+
}
|
|
109
|
+
const roundInfo = await getBroadcastRound(roundId);
|
|
110
|
+
if (!roundInfo) {
|
|
111
|
+
console.error(cl.red("Round not found."));
|
|
112
|
+
exit(1);
|
|
113
|
+
}
|
|
114
|
+
const loopArgIndex = args.findIndex((arg) => arg === "--loop");
|
|
115
|
+
let loopTimer = undefined;
|
|
116
|
+
if (loopArgIndex !== -1 && loopArgIndex + 1 < args.length) {
|
|
117
|
+
const loopTimerStr = args[loopArgIndex + 1];
|
|
118
|
+
loopTimer = parseInt(loopTimerStr, 10);
|
|
119
|
+
if (isNaN(loopTimer) || loopTimer <= 0) {
|
|
120
|
+
console.error(cl.red("Loop timer must be a positive integer."));
|
|
121
|
+
exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (loopTimer) {
|
|
125
|
+
console.log(cl.green(`Starting loop to push PGN every ${cl.whiteBold(loopTimer.toString())} seconds...`));
|
|
126
|
+
console.log(cl.blue("Press Ctrl+C to stop."));
|
|
127
|
+
await loop(roundInfo, pgnPath, loopTimer, filterIds);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const pgnContent = await readPGNFromURL(pgnPath, filterIds);
|
|
131
|
+
if (pgnContent)
|
|
132
|
+
await pushPGN(roundInfo, pgnContent);
|
|
133
|
+
}
|
|
134
|
+
};
|