Advertisement
arekay115

solo stats csv

Apr 22nd, 2025
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.35 KB | None | 0 0
  1. // ==UserScript==
  2. // @name solo stats csv
  3. // @namespace http://tampermonkey.net/
  4. // @version 6.0
  5. // @description Fetches Geoguessr API data to get advanced stats for solo games and displays them on the profile page.
  6. // @author You
  7. // @match https://www.geoguessr.com/*/me/profile
  8. // @match https://www.geoguessr.com/me/profile
  9. // @icon https://www.geoguessr.com/_next/static/media/favicon.bffdd9d3.png
  10.  
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_addStyle
  13. // @require https://code.jquery.com/jquery-3.6.0.min.js
  14. // @require https://unpkg.com/[email protected]/dist/axios.min.js
  15. // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/progressbar.min.js
  16. // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js
  17. // @connect https://game-server.geoguessr.com/
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. //*** Constants and Variables ***//
  24. // Constants
  25. const API_BASE_URL = 'https://www.geoguessr.com/api/v3/games';
  26.  
  27. // Variables
  28. let playerId = '';
  29.  
  30.  
  31. //*** Helper Functions ***//
  32. // Function to convert JSON to CSV
  33. function jsonToCsv(json) {
  34. console.log(json);
  35.  
  36. const reversedJson = json.slice().reverse(); // Reverse the JSON array
  37.  
  38. // Define new header names
  39. const newHeaders = {
  40. time: "game_datetime",
  41. mapName: "map",
  42. points: "game_score",
  43. gameMode: "game_mode",
  44. isDailyChallenge: "is_daily_challenge",
  45. gameToken: "game_token",
  46. challengeToken: "challenge_token"
  47. };
  48.  
  49. // Replace keys with new header names
  50. const keys = Object.keys(reversedJson[0]);
  51. const renamedHeaders = keys.map(key => newHeaders[key] || key); // Use new headers if defined, otherwise keep original
  52.  
  53. const csv = reversedJson.map(row =>
  54. keys.map(key => JSON.stringify(row[key], (_, value) => value === null ? '' : value)).join(',')
  55. );
  56.  
  57. return [renamedHeaders.join(','), ...csv].join('\r\n'); // Use renamed headers for the header row
  58. }
  59.  
  60. function jsonToCsvForSecondCsv(json, newHeaders) {
  61. console.log(json);
  62.  
  63. // Extract original keys from the data
  64. const keys = Object.keys(json[0]); // Correctly reference the 'json' parameter
  65.  
  66. // Replace original keys with new headers (or keep original if no mapping exists)
  67. const renamedHeaders = keys.map(key => newHeaders[key] || key);
  68.  
  69. // Generate CSV rows using renamed headers
  70. const csv = json.map(row => // Use 'json' directly here
  71. keys.map(key => JSON.stringify(row[key], (_, value) => value === null ? '' : value)).join(',')
  72. );
  73.  
  74. return [renamedHeaders.join(','), ...csv].join('\r\n'); // Include renamed headers in the CSV
  75. }
  76.  
  77.  
  78. //delay setup
  79. function delay(ms) {
  80. return new Promise(resolve => setTimeout(resolve, ms));
  81. }
  82.  
  83. // Function to download a CSV file
  84. function downloadCsv(data, filename) {
  85. const blob = new Blob([data], { type: 'text/csv;charset=utf-8;' });
  86. const url = URL.createObjectURL(blob);
  87. const link = document.createElement("a");
  88. link.setAttribute("href", url);
  89.  
  90. // Correct replacement string
  91. const baseFilename = filename.replace(/ - (\d{4}-\d{2}-\d{2})T(\d{2})(\d{2})(\d{2})\.\d{3}/, ' - $1-$2$3');
  92. link.setAttribute("download", baseFilename);
  93.  
  94. link.style.visibility = 'hidden';
  95. document.body.appendChild(link);
  96. link.click();
  97. document.body.removeChild(link);
  98. }
  99.  
  100. // Function to count new games
  101. function countNewGames(gameIds, lastDownloadDate) {
  102. if (!lastDownloadDate) {
  103. // If no previous timestamp is found, all games are considered "new"
  104. return gameIds.length;
  105. }
  106.  
  107. // Count the games with timestamps later than the last download timestamp
  108. const newGames = gameIds.filter(game => new Date(game.time) > lastDownloadDate);
  109. console.log(`${newGames.length} new games found since last download.`);
  110. return newGames.length;
  111. }
  112.  
  113. //*** API Interactions ***//
  114. // Function to get game tokens- Phase 1
  115. async function getStandardGameTokens(session) {
  116. const startTime = new Date();
  117. console.log("getStandardGameTokens started at:", startTime.toLocaleString());
  118.  
  119. const gameTokens = [];
  120. let paginationToken = null;
  121.  
  122. try {
  123. while (true) {
  124. const response = await session.get('https://www.geoguessr.com/api/v4/feed/private', { params: { paginationToken } });
  125. const data = response.data;
  126. paginationToken = data.paginationToken;
  127.  
  128. if (!playerId && data.entries.length > 0) {
  129. playerId = data.entries[0].user.id;
  130. console.log("Player ID:", playerId);
  131. }
  132.  
  133. data.entries.forEach(entry => {
  134. try {
  135. extractStandardGameTokens(entry, gameTokens);
  136. } catch (error) {
  137. console.error(error);
  138. }
  139. });
  140.  
  141. if (!paginationToken) break;
  142. }
  143. } catch (error) {
  144. console.error("An error occurred while fetching game tokens:", error);
  145. }
  146. console.log("Number of Game Tokens found:", gameTokens.length);
  147.  
  148. const endTimePhase1 = new Date();
  149. const durationPhase1 = (endTimePhase1 - startTime) / 1000;
  150. console.log("getStandardGameTokens finished at:", endTimePhase1.toLocaleString());
  151. console.log("Time taken for getStandardGameTokens:", durationPhase1, "seconds");
  152.  
  153. const gameTokensCsv = jsonToCsv(gameTokens.map(gameToken => ({
  154. time: gameToken.time,
  155. mapName: gameToken.mapName,
  156. points: gameToken.points,
  157. gameMode: gameToken.gameMode,
  158. isDailyChallenge: gameToken.isDailyChallenge,
  159. gameToken: gameToken.gameToken,
  160. challengeToken: gameToken.challengeToken,
  161. })));
  162.  
  163. downloadCsv(gameTokensCsv, 'newGames.csv');
  164. return gameTokens;
  165. }
  166.  
  167. // extract game tokens
  168. function extractStandardGameTokens(entry, gameTokens) {
  169. try {
  170. const payloadJson = JSON.parse(entry.payload);
  171. const entryTime = entry.time; // Store entry time for fallback
  172.  
  173. if (Array.isArray(payloadJson)) {
  174. payloadJson.forEach(payloadItem => {
  175. let gameData = null;
  176. let time = entryTime; // Default to entry time
  177. let gameToken = null;
  178. let challengeToken = null;
  179.  
  180. if (payloadItem.payload && payloadItem.payload.gameMode === 'Standard') {
  181. gameData = payloadItem.payload;
  182. time = payloadItem.time || entryTime; // Use payload time if available, otherwise fallback
  183. gameToken = gameData.gameToken;
  184. challengeToken = gameData.challengeToken;
  185.  
  186. } else if (payloadItem.gameMode === 'Standard') {
  187. gameData = payloadItem;
  188. time = payloadItem.time || entryTime; // Use payload time if available, otherwise fallback
  189. gameToken = gameData.gameToken;
  190. challengeToken = gameData.challengeToken;
  191. }
  192.  
  193. if (gameData) {
  194. const mapName = gameData.mapName;
  195. const points = gameData.points;
  196. const gameMode = gameData.gameMode;
  197. const isDailyChallenge = gameData.isDailyChallenge;
  198.  
  199. gameTokens.push({
  200. time: time,
  201. mapName: mapName,
  202. points: points,
  203. gameMode: gameMode,
  204. isDailyChallenge: isDailyChallenge,
  205. gameToken: gameToken,
  206. challengeToken: challengeToken,
  207. });
  208. }
  209. });
  210. } else if (payloadJson && payloadJson.gameMode === 'Standard') {
  211. const time = entry.time;
  212. const mapName = payloadJson.mapName;
  213. const points = payloadJson.points;
  214. const gameMode = payloadJson.gameMode;
  215. const isDailyChallenge = payloadJson.isDailyChallenge;
  216. const gameToken = payloadJson.gameToken;
  217. const challengeToken = payloadJson.challengeToken;
  218.  
  219.  
  220. gameTokens.push({
  221. time: time,
  222. mapName: mapName,
  223. points: points,
  224. gameMode: gameMode,
  225. isDailyChallenge: isDailyChallenge,
  226. gameToken: gameToken,
  227. challengeToken: challengeToken,
  228. });
  229. }
  230.  
  231. } catch (error) {
  232. console.error("Error parsing payload:", error, entry.payload);
  233. }
  234. }
  235.  
  236. // function to fetch duels - Phase 2 - iterates through the gameIds and feches data
  237. async function fetchStandardGames(session, gameTokens) {
  238. const games = [];
  239. for (const game of gameTokens) { // Use gameTokens
  240. try {
  241. const response = await session.get(`${API_BASE_URL}/${game.token}`); // Use game.token
  242. games.push(response.data);
  243. await delay(1000); // Keep the delay
  244. } catch (error) {
  245. console.error(`Error fetching game data for token ${game.token}:`, error); // Use game.token
  246. await delay(1000);
  247. }
  248. }
  249. return games;
  250. }
  251.  
  252.  
  253. //***Data Processing ***//
  254. // Function to extract player round data from duel JSON - Phase 2 - Processes data from fetchduels
  255. function extractStandardGameData(games) {
  256. const allRoundsData = [];
  257.  
  258. games.forEach(game => {
  259. game.rounds.forEach((round, roundIndex) => { // Add roundIndex
  260. const guess = game.player.guesses[roundIndex] || {}; // Get guess for this round, handle missing guesses
  261.  
  262. allRoundsData.push({
  263. token: game.token,
  264. roundCount: game.roundCount,
  265. timeLimit: game.timeLimit,
  266. roundStartTime: round.startTime,
  267. mapName: game.mapName,
  268. forbidMoving: game.forbidMoving,
  269. forbidZooming: game.forbidZooming,
  270. forbidRotating: game.forbidRotating,
  271. roundNumber: roundIndex+1,
  272. roundLat: round.lat,
  273. roundLng: round.lng,
  274. countryStreakCode: round.streakLocationCode || null,
  275. guessLat: guess.lat || null, // Add guess latitude
  276. guessLng: guess.lng || null, // Add guess longitude
  277. roundScoreInPoints: guess.roundScoreInPoints || null, // Add round score
  278. distanceInMeters: guess.distanceInMeters || null, // Add distance
  279. guessTime: guess.time || null, // Add guess time
  280. type: game.type ?? null,
  281. mode: game.mode ?? null,
  282. state: game.state ?? null,
  283. timedOut: guess.timedOut || null, // Add timedOut
  284. timedOutWithGuess: guess.timedOutWithGuess || null, // Add timedOutWithGuess
  285. skippedRound: guess.skippedRound || null // Add skippedRound
  286. });
  287. });
  288. });
  289.  
  290. // After the allRoundsData array is populated in extractStandardGameData
  291. allRoundsData.sort((a, b) => new Date(a.roundStartTime) - new Date(b.roundStartTime));
  292.  
  293. return allRoundsData;
  294. }
  295.  
  296. //*** Event Handlers ***//
  297. //function to handle CSV button click - Phase 2 - called when user confirms number duels to download
  298. async function handleDownloadCsv(session, gameTokens) {
  299. const numGames = gameTokens.length;
  300.  
  301. const progressPopup = document.createElement('div');
  302. const topOffset = 80;
  303. const rightOffset = 80;
  304.  
  305. progressPopup.style.cssText = `
  306. position: fixed;
  307. top: ${topOffset}px;
  308. right: ${rightOffset}px;
  309. transform: translate(0, 0);
  310. background-color: #fff;
  311. padding: 20px;
  312. border: 1px solid #ccc;
  313. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  314. z-index: 10003;
  315. width: auto;
  316. min-width: 300px;
  317. `;
  318.  
  319. progressPopup.innerHTML = `
  320. <p><span id="gamesFetchedCount">0</span> of ${numGames} fetched</p>
  321. <div id="progressBar"></div>
  322. <button id="cancelFetchButton" style="background-color: red; color: white; padding: 10px 20px; margin-top: 10px;">Cancel</button>
  323. `;
  324.  
  325. document.body.appendChild(progressPopup);
  326.  
  327. const progressBar = new ProgressBar.Line('#progressBar', {
  328. strokeWidth: 4,
  329. easing: 'easeInOut',
  330. duration: 1400,
  331. color: '#007bff',
  332. trailColor: '#eee',
  333. svgStyle: { width: '100%', height: '10px' },
  334. text: {
  335. style: {
  336. color: '#333',
  337. position: 'absolute',
  338. left: '50%',
  339. top: '50%',
  340. padding: 0,
  341. margin: 0,
  342. transform: {
  343. prefix: true,
  344. x: '-50%',
  345. y: '-50%'
  346. }
  347. },
  348. autoStyleContainer: false
  349. },
  350. step: (state, bar) => {
  351. bar.setText(Math.round(bar.value() * 100) + ' %');
  352. }
  353. });
  354.  
  355.  
  356. let fetchedCount = 0;
  357. const games = [];
  358. let cancelled = false;
  359.  
  360. const cancelFetchButton = document.getElementById('cancelFetchButton');
  361. cancelFetchButton.onclick = () => {
  362. cancelled = true;
  363. document.body.removeChild(progressPopup);
  364. console.log("Game fetching cancelled by user.");
  365. };
  366.  
  367.  
  368. for (let i = 0; i < numGames; i++) {
  369. if (cancelled) break;
  370.  
  371. try {
  372. const response = await session.get(`${API_BASE_URL}/${gameTokens[i].gameToken}`); // Use gameTokens[i].gameToken
  373. games.push(response.data);
  374. fetchedCount++;
  375. document.getElementById('gamesFetchedCount').textContent = fetchedCount;
  376. progressBar.set(fetchedCount / numGames);
  377. await delay(1000);
  378. } catch (error) {
  379. console.error(`Error fetching game data for token ${gameTokens[i].gameToken}:`, error); // Use gameTokens[i].gameToken
  380. await delay(1000);
  381. }
  382. }
  383.  
  384. if (!cancelled) {
  385. document.body.removeChild(progressPopup);
  386.  
  387. const standardGameData = extractStandardGameData(games);
  388.  
  389. // Add the newHeaders object here
  390. const newHeaders = {
  391. token: "game_token",
  392. roundCount: "round_count",
  393. timeLimit: "time_limit",
  394. roundStartTime: "round_start_datetime",
  395. mapName: "map",
  396. forbidMoving: "forbid_moving",
  397. forbidZooming: "forbid_zooming",
  398. forbidRotating: "forbid_rotating",
  399. roundNumber: "game_round_number",
  400. roundLat: "loc_lat",
  401. roundLng: "loc_lng",
  402. countryStreakCode: "loc_cc",
  403. guessLat: "guess_lat",
  404. guessLng: "guess_lng",
  405. roundScoreInPoints: "score",
  406. distanceInMeters: "distance",
  407. guessTime: "guess_time",
  408. type: "game_type",
  409. mode: "game_mode",
  410. state: "game_state",
  411. timedOut: "timed_out",
  412. timedOutWithGuess: "timed_out_with_guess",
  413. skippedRound: "skipped_round"
  414. };
  415.  
  416. const csvData = jsonToCsvForSecondCsv(standardGameData, newHeaders);
  417. downloadCsv(csvData, 'newGamesRounds.csv');
  418. console.log("Standard games stats download complete.");
  419. }
  420.  
  421. }
  422.  
  423. //*** UI Popups ***//
  424. //Show confirmation popup at page load
  425. function showConfirmationPopup() {
  426. return new Promise((resolve) => {
  427. const popup = document.createElement('div');
  428. const topOffset = 80; // 80px from the top
  429. const rightOffset = 80; // 80px from the right
  430.  
  431. popup.style.cssText = `
  432. position: fixed;
  433. top: ${topOffset}px;
  434. right: ${rightOffset}px;
  435. transform: translate(0, 0); /* Remove default centering transform */
  436. background-color: #fff;
  437. padding: 20px;
  438. border: 1px solid #ccc;
  439. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  440. z-index: 10000;
  441. `;
  442.  
  443. popup.innerHTML = `
  444. <p>Do You Want to Fetch Solo Game Data?</p>
  445. <button id="confirmButton" style="background-color: green; color: white; padding: 10px 20px; margin-right: 10px;">Yes ✓</button>
  446. <button id="cancelButton" style="background-color: red; color: white; padding: 10px 20px;">No X</button>
  447. `;
  448.  
  449. document.body.appendChild(popup);
  450.  
  451. const confirmButton = document.getElementById('confirmButton');
  452. const cancelButton = document.getElementById('cancelButton');
  453.  
  454. confirmButton.onclick = () => {
  455. document.body.removeChild(popup);
  456. resolve(true); // Resolve with true when confirmed
  457. };
  458.  
  459. cancelButton.onclick = () => {
  460. document.body.removeChild(popup);
  461. resolve(false); // Resolve with false when canceled
  462. };
  463. });
  464. }
  465.  
  466. //Sow searching popup
  467. function showSearchingPopup() {
  468. const popup = document.createElement('div');
  469. const topOffset = 80;
  470. const rightOffset = 80;
  471.  
  472. popup.style.cssText = `
  473. position: fixed;
  474. top: ${topOffset}px;
  475. right: ${rightOffset}px;
  476. transform: translate(0, 0);
  477. background-color: #fff; /* White background */
  478. padding: 20px;
  479. border: 1px solid #ccc;
  480. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  481. z-index: 10001;
  482. width: auto; /* Adjust width as needed */
  483. min-width: 200px; /* Minimum width to prevent it from being too small */
  484. `;
  485. popup.innerHTML = `<p>Searching For Games...</p>`;
  486. document.body.appendChild(popup);
  487. return popup;
  488. }
  489.  
  490. //Hide popup
  491. function hidePopup(popup) {
  492. if (popup && popup.parentNode) { // Check if the popup exists and is in the DOM
  493. popup.parentNode.removeChild(popup);
  494. }
  495. }
  496.  
  497. //Show phase 1 complete popup
  498. function showPhase1CompletePopup(gameIds, lastDownloadDate) {
  499. return new Promise((resolve) => {
  500. const popup = document.createElement('div');
  501. const topOffset = 80;
  502. const rightOffset = 80;
  503.  
  504. popup.style.cssText = `
  505. position: fixed;
  506. top: ${topOffset}px;
  507. right: ${rightOffset}px;
  508. transform: translate(0, 0);
  509. background-color: #fff !important; /* White background - override other styles */
  510. padding: 20px;
  511. border: 1px solid #ccc;
  512. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  513. z-index: 10002;
  514. width: auto;
  515. min-width: 250px;
  516. `;
  517.  
  518. const newGamesCount = countNewGames(gameIds, lastDownloadDate); // Use helper function to count new games
  519. const lastDownloadText = lastDownloadDate
  520. ? `Last download occurred on ${lastDownloadDate.toLocaleString()}`
  521. : "No previous downloads detected"; // Dynamic message
  522.  
  523. popup.innerHTML = `
  524. <p>${gameIds.length} Solo Games Found and Downloaded</p>
  525. <p>${lastDownloadText}</p>
  526. <p>${newGamesCount} new games have occurred since then</p>
  527. <p>How many do you want to fetch?</p>
  528. <input type="number" id="numGames" value="${newGamesCount}" min="1" max="${gameIds.length}" style="width: 100px; margin-right: 10px;"> <!-- Adjusted width -->
  529. <button id="confirmButton" style="background-color: green; color: white; padding: 10px 20px; margin-right: 10px;">Confirm</button>
  530. <button id="cancelButton" style="background-color: red; color: white; padding: 10px 20px;">Cancel</button>
  531. `;
  532.  
  533. document.body.appendChild(popup);
  534.  
  535. const confirmButton = document.getElementById('confirmButton');
  536. const cancelButton = document.getElementById('cancelButton');
  537. const numGamesInput = document.getElementById('numGames'); // Updated input ID
  538.  
  539. confirmButton.onclick = () => {
  540. let numGames = parseInt(numGamesInput.value, 10);
  541.  
  542. if (isNaN(numGames) || numGames < 1) {
  543. alert("Please enter a valid number greater than 0.");
  544. return;
  545. }
  546.  
  547. numGames = Math.min(numGames, gameIds.length);
  548.  
  549. // Save the current timestamp in local storage
  550. localStorage.setItem("lastSoloDownloadTimestamp", JSON.stringify(new Date()));
  551.  
  552. document.body.removeChild(popup);
  553. resolve(numGames);
  554. };
  555.  
  556. cancelButton.onclick = () => {
  557. document.body.removeChild(popup);
  558. resolve(false);
  559. };
  560. });
  561. }
  562. //*** Initialization ***//
  563. //Initialization
  564. async function init() {
  565. // Retrieve the last download timestamp from local storage
  566. const lastDownloadTimestamp = localStorage.getItem("lastSoloDownloadTimestamp");
  567. let lastDownloadDate = null;
  568.  
  569. if (lastDownloadTimestamp) {
  570. lastDownloadDate = new Date(JSON.parse(lastDownloadTimestamp)); // Parse the timestamp into a Date object
  571. console.log("Last download occurred on:", lastDownloadDate.toLocaleString());
  572. } else {
  573. console.log("No previous downloads detected.");
  574. }
  575.  
  576. const userConfirmed = await showConfirmationPopup();
  577.  
  578. if (userConfirmed) {
  579. const scriptStartTime = performance.now();
  580. console.time('total_script_time');
  581. console.log("Script execution started at:", new Date().toLocaleString());
  582.  
  583. const session = axios.create({
  584. withCredentials: true
  585. });
  586.  
  587. const searchingPopup = showSearchingPopup();
  588.  
  589. getStandardGameTokens(session).then(standardGameTokens => {
  590. const phase1EndTime = performance.now();
  591. const phase1Duration = (phase1EndTime - scriptStartTime) / 1000;
  592.  
  593. console.log("Phase 1 (Game Tokens and CSV Download) finished at:", new Date().toLocaleString());
  594.  
  595. hidePopup(searchingPopup);
  596.  
  597. showPhase1CompletePopup(standardGameTokens, lastDownloadDate).then(numGames => {
  598. if (numGames) {
  599. const gameTokensOnly = standardGameTokens.filter(tokenObj => tokenObj.gameToken !== undefined);
  600. const recentGameTokens = gameTokensOnly.slice(0, numGames);
  601.  
  602. const phase2StartTime = performance.now();
  603. console.time('phase2_loading');
  604. console.log("Phase 2 started at:", new Date().toLocaleString());
  605.  
  606. handleDownloadCsv(session, recentGameTokens).then(() => {
  607. const phase2EndTime = performance.now();
  608. const phase2Duration = (phase2EndTime - phase2StartTime) / 1000;
  609.  
  610. console.timeEnd('phase2_loading');
  611. console.timeEnd('total_script_time');
  612.  
  613. const scriptEndTime = performance.now();
  614. const scriptDuration = (scriptEndTime - scriptStartTime) / 1000;
  615.  
  616. console.log("Phase 2 finished at:", new Date().toLocaleString());
  617. console.log("Total time for Phase 2:", phase2Duration, "seconds");
  618. console.log("Total script execution time:", scriptDuration, "seconds");
  619.  
  620. }).catch(error => {
  621. console.timeEnd('phase2_loading');
  622. console.timeEnd('total_script_time');
  623.  
  624. const scriptEndTime = performance.now();
  625. const scriptDuration = (scriptEndTime - scriptStartTime) / 1000;
  626.  
  627. console.log("Script finished at:", new Date().toLocaleString());
  628. console.log("Total script execution time:", scriptDuration, "seconds");
  629. });
  630.  
  631. console.log(`Fetching ${numGames} games...`);
  632. } else {
  633. console.log("User chose not to proceed to Phase 2.");
  634. console.timeEnd('total_script_time');
  635.  
  636. const scriptEndTime = performance.now();
  637. const scriptDuration = (scriptEndTime - scriptStartTime) / 1000;
  638.  
  639. console.log("Script finished at:", new Date().toLocaleString());
  640. console.log("Total script execution time:", scriptDuration, "seconds");
  641. }
  642. }).catch(error => {
  643. console.timeEnd('total_script_time');
  644.  
  645. const scriptEndTime = performance.now();
  646. const scriptDuration = (scriptEndTime - scriptStartTime) / 1000;
  647.  
  648. console.log("Script finished at:", new Date().toLocaleString());
  649. console.log("Total script execution time:", scriptDuration, "seconds");
  650. });
  651.  
  652. }).catch(error => {
  653. console.timeEnd('total_script_time');
  654.  
  655. const scriptEndTime = performance.now();
  656. const scriptDuration = (scriptEndTime - scriptStartTime) / 1000;
  657.  
  658. console.log("Script finished at:", new Date().toLocaleString());
  659. console.log("Total script execution time:", scriptDuration, "seconds");
  660. });
  661.  
  662. } else {
  663. console.log("Script execution cancelled by user.");
  664. }
  665. }
  666. $(document).ready(init);
  667.  
  668. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement