@20syldev/api 3.4.3 → 3.4.5

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.
@@ -1,167 +1,236 @@
1
- import { randomBytes } from 'crypto';
2
-
3
- /**
4
- * Manages a Tic-Tac-Toe game session
5
- *
6
- * @param {string} action - The action to perform ('play' or 'fetch')
7
- * @param {Object} params - The parameters for the action
8
- * @returns {Object} - The result of the action
9
- * @throws {Error} - If parameters are invalid
10
- */
11
- export default function tic_tac_toe(action, params = {}) {
12
- // In-memory storage
13
- const storage = params.storage || {};
14
-
15
- // Initialize storage
16
- storage.games ??= {};
17
- storage.sessions ??= {};
18
- storage.rateLimits ??= {};
19
-
20
- // Reference the storage properties
21
- const { games, sessions, rateLimits } = storage;
22
-
23
- // Input validation for common parameters
24
- if (!params.username) throw new Error('Please provide a username');
25
-
26
- const u = params.username.toLowerCase();
27
- const now = Date.now();
28
-
29
- // Rate limiting
30
- rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
31
- if (rateLimits[u].length > 50) {
32
- const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
33
- throw new Error(`Rate limit exceeded. Try again in ${remainingTime} seconds.`);
34
- }
35
- rateLimits[u].push(now);
36
-
37
- // Handle different actions
38
- if (action === 'play') {
39
- return playMove(params, games, sessions, u, now);
40
- } else if (action === 'fetch') {
41
- return fetchGame(params, games, u);
42
- } else {
43
- throw new Error('Invalid action. Use "play" or "fetch"');
44
- }
45
- }
46
-
47
- /**
48
- * Process a player's move
49
- */
50
- function playMove(params, games, sessions, u, now) {
51
- const { move, session, game } = params;
52
- const validMoves = ['1-1', '1-2', '1-3', '2-1', '2-2', '2-3', '3-1', '3-2', '3-3'];
53
-
54
- // Input validation for play action
55
- if (!move) throw new Error('Please provide a valid move');
56
- if (!session) throw new Error('Please provide a valid session ID');
57
- if (!game) throw new Error('Please provide a valid game ID');
58
- if (!validMoves.includes(move)) throw new Error('Invalid move. Please provide a valid move (e.g., 1-1, 2-2, 3-3).');
59
-
60
- // Session validation
61
- if (sessions[u] && sessions[u].user !== session) {
62
- throw new Error('Session ID mismatch');
63
- }
64
-
65
- // Initialize game if needed
66
- games[game] = games[game] || [];
67
-
68
- // Check if game is full
69
- const players = [...new Set(games[game].map(play => play.username))];
70
- if (players.length >= 2 && !players.includes(params.username)) {
71
- throw new Error('Game is full, you can only watch.');
72
- }
73
-
74
- // Check if it's player's turn
75
- if (games[game].length > 0 && games[game][games[game].length - 1].username === params.username) {
76
- throw new Error('Please wait for the other player to make a move.');
77
- }
78
-
79
- // Check if move is already made
80
- if (games[game].some(play => play.move === move)) {
81
- throw new Error('Move already made. Please choose a different move.');
82
- }
83
-
84
- // Add move to game
85
- const play = { username: params.username, move, session };
86
- games[game].push(play);
87
-
88
- // Check game result
89
- const result = checkGame(games[game]);
90
- if (result.winner || result.tie) {
91
- setTimeout(() => delete games[game], 600000);
92
- return {
93
- message: `Move sent successfully. ${result.winner ? result.winner + ' wins. ' + result.loser + ' loses.' : 'It\'s a tie.'}`,
94
- ...result
95
- };
96
- }
97
-
98
- // Set cleanup timeout
99
- setTimeout(() => delete games[game], 3600000);
100
-
101
- // Update session
102
- sessions[u] = sessions[u] || { user: session, last: now };
103
- sessions[u].last = now;
104
- setTimeout(() => { if (now - sessions[u].last >= 3600000) delete sessions[u]; }, 3600000);
105
-
106
- return { message: 'Move sent successfully' };
107
- }
108
-
109
- /**
110
- * Fetch a game state
111
- */
112
- function fetchGame(params, games, u) {
113
- const ID = params.game || generateGameId();
114
-
115
- // Initialize game if needed
116
- if (!games[ID]) games[ID] = [];
117
-
118
- const data = games[ID];
119
- const last = data.length ? data[data.length - 1].username : null;
120
- const players = [...new Set(data.map(p => p.username))];
121
- const turn = players.find(p => p !== last);
122
- const result = data.length ? checkGame(data) : {};
123
-
124
- return { game: data, turn, ID, ...result };
125
- }
126
-
127
- /**
128
- * Generate a random game ID
129
- */
130
- function generateGameId() {
131
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
132
- return Array.from(randomBytes(5)).map(b => chars[b % chars.length]).join('');
133
- }
134
-
135
- /**
136
- * Check the game result
137
- */
138
- function checkGame(moves) {
139
- let board = Array(3).fill().map(() => Array(3).fill(null));
140
- let playerSymbols = {};
141
- let playersOrder = [];
142
-
143
- moves.forEach(({ username, move }) => {
144
- if (!playerSymbols[username]) {
145
- playersOrder.push(username);
146
- playerSymbols[username] = playersOrder.length === 1 ? 'X' : 'O';
147
- }
148
- let [row, col] = move.split('-').map(Number);
149
- board[row - 1][col - 1] = playerSymbols[username];
150
- });
151
-
152
- const checkWinner = (symbol) => {
153
- for (let i = 0; i < 3; i++) {
154
- if (board[i][0] === symbol && board[i][1] === symbol && board[i][2] === symbol) return true;
155
- if (board[0][i] === symbol && board[1][i] === symbol && board[2][i] === symbol) return true;
156
- }
157
- if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) return true;
158
- if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) return true;
159
- return false;
160
- };
161
-
162
- let winner = Object.keys(playerSymbols).find(player => checkWinner(playerSymbols[player]));
163
- let isTie = !winner && moves.length === 9;
164
- let loser = winner && playersOrder.length === 2 ? playersOrder.find(player => player !== winner) : null;
165
-
166
- return { winner, loser, tie: isTie };
1
+ import { randomBytes } from 'crypto';
2
+
3
+ /**
4
+ * Manages a Tic-Tac-Toe game session
5
+ *
6
+ * @param {string} action - The action to perform ('play', 'fetch', or 'list')
7
+ * @param {Object} params - The parameters for the action
8
+ * @returns {Object} - The result of the action
9
+ * @throws {Error} - If parameters are invalid
10
+ */
11
+ export default function tic_tac_toe(action, params = {}) {
12
+ // In-memory storage
13
+ const storage = params.storage || {};
14
+
15
+ // Initialize storage
16
+ storage.games ??= {};
17
+ storage.sessions ??= {};
18
+ storage.rateLimits ??= {};
19
+
20
+ // Reference the storage properties
21
+ const { games, sessions, rateLimits } = storage;
22
+
23
+ // List public games
24
+ if (action === 'list') return listGames(games);
25
+
26
+ // Input validation for common parameters
27
+ if (!params.username) throw new Error('Please provide a username');
28
+
29
+ const u = params.username.toLowerCase();
30
+ const now = Date.now();
31
+
32
+ // Rate limiting
33
+ rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
34
+ if (rateLimits[u].length > 50) {
35
+ const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
36
+ throw new Error(`Rate limit exceeded. Try again in ${remainingTime} seconds.`);
37
+ }
38
+ rateLimits[u].push(now);
39
+
40
+ // Handle different actions
41
+ if (action === 'play') {
42
+ return playMove(params, games, sessions, u, now);
43
+ } else if (action === 'fetch') {
44
+ return fetchGame(params, games, u);
45
+ } else {
46
+ throw new Error('Invalid action. Use "play", "fetch", or "list"');
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Process a player's move
52
+ */
53
+ function playMove(params, games, sessions, u, now) {
54
+ const { move, session, game } = params;
55
+ const validMoves = ['1-1', '1-2', '1-3', '2-1', '2-2', '2-3', '3-1', '3-2', '3-3'];
56
+
57
+ // Input validation for play action
58
+ if (!move) throw new Error('Please provide a valid move');
59
+ if (!session) throw new Error('Please provide a valid session ID');
60
+ if (!game) throw new Error('Please provide a valid game ID');
61
+ if (!validMoves.includes(move)) throw new Error('Invalid move. Please provide a valid move (e.g., 1-1, 2-2, 3-3).');
62
+
63
+ // Session validation
64
+ if (sessions[u] && sessions[u].user !== session) {
65
+ throw new Error('Session ID mismatch');
66
+ }
67
+
68
+ // Initialize game if needed
69
+ games[game] = games[game] || {
70
+ moves: [],
71
+ players: [],
72
+ private: params.private || false,
73
+ creation: Date.now()
74
+ };
75
+ const moves = games[game].moves;
76
+
77
+ // Check if game is full
78
+ const players = [...new Set(moves.map(play => play.username))];
79
+ if (players.length >= 2 && !players.includes(params.username)) {
80
+ throw new Error('Game is full, you can only watch.');
81
+ }
82
+
83
+ // Check if it's player's turn
84
+ if (moves.length > 0 && moves[moves.length - 1].username === params.username) {
85
+ throw new Error('Please wait for the other player to make a move.');
86
+ }
87
+
88
+ // Check if move is already made
89
+ if (moves.some(play => play.move === move)) {
90
+ throw new Error('Move already made. Please choose a different move.');
91
+ }
92
+
93
+ // Add move to game
94
+ const play = { username: params.username, move, session };
95
+ moves.push(play);
96
+
97
+ // Check game result
98
+ const result = checkGame(moves);
99
+ if (result.winner || result.tie) {
100
+ setTimeout(() => delete games[game], 600000);
101
+ return {
102
+ message: `Move sent successfully. ${result.winner ? result.winner + ' wins. ' + result.loser + ' loses.' : 'It\'s a tie.'}`,
103
+ ...result
104
+ };
105
+ }
106
+
107
+ // Set cleanup timeout
108
+ setTimeout(() => delete games[game], 3600000);
109
+
110
+ // Update session
111
+ sessions[u] = sessions[u] || { user: session, last: now };
112
+ sessions[u].last = now;
113
+ setTimeout(() => { if (now - sessions[u].last >= 3600000) delete sessions[u]; }, 3600000);
114
+
115
+ return { message: 'Move sent successfully' };
116
+ }
117
+
118
+ /**
119
+ * Fetch a game state
120
+ */
121
+ function fetchGame(params, games, u) {
122
+ const id = params.game || generateGameId();
123
+ if (!games[id]) {
124
+ games[id] = {
125
+ moves: [],
126
+ players: [],
127
+ private: params.private || false,
128
+ creation: Date.now()
129
+ };
130
+ }
131
+ if (!games[id].players.includes(u)) {
132
+ games[id].players.push(u);
133
+ }
134
+
135
+ const moves = games[id].moves;
136
+ const players = games[id].players;
137
+ const privateGame = games[id].private;
138
+ const lastPlayer = moves.length ? moves[moves.length - 1].username : null;
139
+ const turn = players.find(p => p !== lastPlayer) || players[0];
140
+ const status = players.length >= 2 ? 'ready' : 'waiting';
141
+ const result = moves.length ? checkGame(moves) : {};
142
+
143
+ return {
144
+ id,
145
+ moves,
146
+ players,
147
+ turn,
148
+ status,
149
+ private: privateGame,
150
+ ...result
151
+ };
152
+ }
153
+
154
+ /**
155
+ * List all public games in progress
156
+ */
157
+ function listGames(games) {
158
+ const publicGames = [];
159
+ const now = Date.now();
160
+
161
+ for (const [gameId, game] of Object.entries(games)) {
162
+ // Only include public games
163
+ if (!game.private) {
164
+ const result = game.moves.length ? checkGame(game.moves) : {};
165
+ const isFinished = result.winner || result.tie;
166
+
167
+ // Only include active games (not finished)
168
+ if (!isFinished) {
169
+ const lastPlayer = game.moves.length ? game.moves[game.moves.length - 1].username : null;
170
+ const turn = game.players.find(p => p !== lastPlayer) || game.players[0];
171
+ const status = game.players.length >= 2 ? 'ready' : 'waiting';
172
+
173
+ publicGames.push({
174
+ id: gameId,
175
+ players: game.players,
176
+ playersCount: game.players.length,
177
+ moves: game.moves.length,
178
+ turn,
179
+ status,
180
+ creation: game.creation || now
181
+ });
182
+ }
183
+ }
184
+ }
185
+
186
+ // Sort by creation time (newest first)
187
+ publicGames.sort((a, b) => b.creation - a.creation);
188
+
189
+ return {
190
+ message: 'Public games available',
191
+ count: publicGames.length,
192
+ games: publicGames
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Generate a random game ID
198
+ */
199
+ function generateGameId() {
200
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
201
+ return Array.from(randomBytes(5)).map(b => chars[b % chars.length]).join('');
202
+ }
203
+
204
+ /**
205
+ * Check the game result
206
+ */
207
+ function checkGame(moves) {
208
+ let board = Array(3).fill().map(() => Array(3).fill(null));
209
+ let playerSymbols = {};
210
+ let playersOrder = [];
211
+
212
+ moves.forEach(({ username, move }) => {
213
+ if (!playerSymbols[username]) {
214
+ playersOrder.push(username);
215
+ playerSymbols[username] = playersOrder.length === 1 ? 'X' : 'O';
216
+ }
217
+ let [row, col] = move.split('-').map(Number);
218
+ board[row - 1][col - 1] = playerSymbols[username];
219
+ });
220
+
221
+ const checkWinner = (symbol) => {
222
+ for (let i = 0; i < 3; i++) {
223
+ if (board[i][0] === symbol && board[i][1] === symbol && board[i][2] === symbol) return true;
224
+ if (board[0][i] === symbol && board[1][i] === symbol && board[2][i] === symbol) return true;
225
+ }
226
+ if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) return true;
227
+ if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) return true;
228
+ return false;
229
+ };
230
+
231
+ let winner = Object.keys(playerSymbols).find(player => checkWinner(playerSymbols[player]));
232
+ let isTie = !winner && moves.length === 9;
233
+ let loser = winner && playersOrder.length === 2 ? playersOrder.find(player => player !== winner) : null;
234
+
235
+ return { winner, loser, tie: isTie };
167
236
  }
@@ -1,87 +1,87 @@
1
- /**
2
- * Generate time information based on specified parameters.
3
- *
4
- * @param {string} type - The type of time information ('live' or 'random')
5
- * @param {string} start - Start date for random generation (ISO format)
6
- * @param {string} end - End date for random generation (ISO format)
7
- * @param {string} format - Specific time format to return
8
- * @param {string} timezone - Timezone to use
9
- * @returns {object} - Time information in various formats
10
- * @throws {Error} - If inputs are invalid
11
- */
12
- export default function time(type = 'live', start, end, format, timezone) {
13
- const validFormats = [
14
- 'iso', 'utc', 'timestamp', 'locale', 'date', 'time',
15
- 'year', 'month', 'day', 'hour', 'minute', 'second',
16
- 'ms', 'dayOfWeek', 'dayOfYear', 'weekNumber',
17
- 'timezone', 'timezoneOffset'
18
- ];
19
-
20
- const validTimezones = [
21
- 'UTC', 'America/New_York', 'Europe/Paris',
22
- 'Asia/Tokyo', 'Australia/Sydney'
23
- ];
24
-
25
- // Input validation
26
- if (type !== 'live' && type !== 'random') {
27
- throw new Error('Please provide a valid type (live or random)');
28
- }
29
-
30
- if (start && !Date.parse(start)) {
31
- throw new Error('Please provide a valid start date (YYYY-MM-DD)');
32
- }
33
-
34
- if (end && !Date.parse(end)) {
35
- throw new Error('Please provide a valid end date (YYYY-MM-DD)');
36
- }
37
-
38
- if (format && !validFormats.includes(format)) {
39
- throw new Error(`Please provide a valid format. Options: ${validFormats.join(', ')}`);
40
- }
41
-
42
- if (timezone && !validTimezones.includes(timezone)) {
43
- throw new Error(`Please provide a valid timezone. Options: ${validTimezones.join(', ')}`);
44
- }
45
-
46
- // Helper function to get all time formats for a date
47
- const getTimeFormats = (date, tz) => {
48
- return {
49
- iso: date.toISOString(),
50
- utc: date.toUTCString(),
51
- timestamp: date.getTime(),
52
- locale: date.toLocaleString('en-US', { timeZone: tz, timeZoneName: 'long' }),
53
- date: date.toLocaleDateString('en-US', { timeZone: tz }),
54
- time: date.toLocaleTimeString('en-US', { timeZone: tz }),
55
- year: date.getFullYear(),
56
- month: date.getMonth() + 1,
57
- day: date.getDate(),
58
- hour: date.getHours(),
59
- minute: date.getMinutes(),
60
- second: date.getSeconds(),
61
- ms: date.getMilliseconds(),
62
- dayOfWeek: date.getDay(),
63
- dayOfYear: Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000),
64
- weekNumber: Math.ceil((((date - new Date(date.getFullYear(), 0, 0)) / 86400000) + 1) / 7),
65
- timezone: tz,
66
- timezoneOffset: date.getTimezoneOffset()
67
- };
68
- };
69
-
70
- // Generate time information based on type
71
- if (type === 'random') {
72
- const startDate = start ? new Date(start).getTime() : new Date('1900-01-01').getTime();
73
- const endDate = end ? new Date(end).getTime() : new Date('2100-12-31').getTime();
74
- const randomDate = new Date(startDate + Math.random() * (endDate - startDate));
75
- const tz = timezone || validTimezones[Math.floor(Math.random() * validTimezones.length)];
76
- const formats = getTimeFormats(randomDate, tz);
77
-
78
- return format ? { date: formats[format] } : formats;
79
- }
80
-
81
- // Live time information
82
- const now = new Date();
83
- const tz = timezone || 'UTC';
84
- const formats = getTimeFormats(now, tz);
85
-
86
- return format ? { date: formats[format] } : formats;
1
+ /**
2
+ * Generate time information based on specified parameters.
3
+ *
4
+ * @param {string} type - The type of time information ('live' or 'random')
5
+ * @param {string} start - Start date for random generation (ISO format)
6
+ * @param {string} end - End date for random generation (ISO format)
7
+ * @param {string} format - Specific time format to return
8
+ * @param {string} timezone - Timezone to use
9
+ * @returns {object} - Time information in various formats
10
+ * @throws {Error} - If inputs are invalid
11
+ */
12
+ export default function time(type = 'live', start, end, format, timezone) {
13
+ const validFormats = [
14
+ 'iso', 'utc', 'timestamp', 'locale', 'date', 'time',
15
+ 'year', 'month', 'day', 'hour', 'minute', 'second',
16
+ 'ms', 'dayOfWeek', 'dayOfYear', 'weekNumber',
17
+ 'timezone', 'timezoneOffset'
18
+ ];
19
+
20
+ const validTimezones = [
21
+ 'UTC', 'America/New_York', 'Europe/Paris',
22
+ 'Asia/Tokyo', 'Australia/Sydney'
23
+ ];
24
+
25
+ // Input validation
26
+ if (type !== 'live' && type !== 'random') {
27
+ throw new Error('Please provide a valid type (live or random)');
28
+ }
29
+
30
+ if (start && !Date.parse(start)) {
31
+ throw new Error('Please provide a valid start date (YYYY-MM-DD)');
32
+ }
33
+
34
+ if (end && !Date.parse(end)) {
35
+ throw new Error('Please provide a valid end date (YYYY-MM-DD)');
36
+ }
37
+
38
+ if (format && !validFormats.includes(format)) {
39
+ throw new Error(`Please provide a valid format. Options: ${validFormats.join(', ')}`);
40
+ }
41
+
42
+ if (timezone && !validTimezones.includes(timezone)) {
43
+ throw new Error(`Please provide a valid timezone. Options: ${validTimezones.join(', ')}`);
44
+ }
45
+
46
+ // Helper function to get all time formats for a date
47
+ const getTimeFormats = (date, tz) => {
48
+ return {
49
+ iso: date.toISOString(),
50
+ utc: date.toUTCString(),
51
+ timestamp: date.getTime(),
52
+ locale: date.toLocaleString('en-US', { timeZone: tz, timeZoneName: 'long' }),
53
+ date: date.toLocaleDateString('en-US', { timeZone: tz }),
54
+ time: date.toLocaleTimeString('en-US', { timeZone: tz }),
55
+ year: date.getFullYear(),
56
+ month: date.getMonth() + 1,
57
+ day: date.getDate(),
58
+ hour: date.getHours(),
59
+ minute: date.getMinutes(),
60
+ second: date.getSeconds(),
61
+ ms: date.getMilliseconds(),
62
+ dayOfWeek: date.getDay(),
63
+ dayOfYear: Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000),
64
+ weekNumber: Math.ceil((((date - new Date(date.getFullYear(), 0, 0)) / 86400000) + 1) / 7),
65
+ timezone: tz,
66
+ timezoneOffset: date.getTimezoneOffset()
67
+ };
68
+ };
69
+
70
+ // Generate time information based on type
71
+ if (type === 'random') {
72
+ const startDate = start ? new Date(start).getTime() : new Date('1900-01-01').getTime();
73
+ const endDate = end ? new Date(end).getTime() : new Date('2100-12-31').getTime();
74
+ const randomDate = new Date(startDate + Math.random() * (endDate - startDate));
75
+ const tz = timezone || validTimezones[Math.floor(Math.random() * validTimezones.length)];
76
+ const formats = getTimeFormats(randomDate, tz);
77
+
78
+ return format ? { date: formats[format] } : formats;
79
+ }
80
+
81
+ // Live time information
82
+ const now = new Date();
83
+ const tz = timezone || 'UTC';
84
+ const formats = getTimeFormats(now, tz);
85
+
86
+ return format ? { date: formats[format] } : formats;
87
87
  }