diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3ec544c7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env \ No newline at end of file diff --git a/background.js b/background.js index 93473d422..f082122f5 100644 --- a/background.js +++ b/background.js @@ -245,6 +245,7 @@ chrome.windows.onFocusChanged.addListener(function (wId) { chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { //console.log(message); //console.log(sender); + console.log('Message received in background:', message); switch (message.action || message.name || message) { case 'play': @@ -319,8 +320,71 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { } catch (error) { console.error(error); } } else { console.error('Permission is not granted.'); } }) - break - } + break; + + case 'store-video-id': + //console.log('Storing video ID:', message.videoId); // Debugging log + if (message.videoId) { + chrome.storage.local.set({ videoId: message.videoId }, function() { + //console.log('Video ID stored:', message.videoId); // Debugging log + sendResponse({ status: 'success' }); + }); + } else { + sendResponse({ status: 'error', message: 'No video ID provided' }); + } + return true; // Indicates that sendResponse will be called asynchronously + + case 'check-switch-state': + chrome.storage.local.get('switchState', function(result) { + //console.log('Switch state:', result.switchState); + sendResponse( {isSwitchOn: result.switchState}); + }); + return true; + + case 'fetch-new-data': + chrome.storage.local.get('videoId', async function(result) { + const videoId = result.videoId; + const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key + try { + const videoInfo = await getVideoInfo(apiKey, videoId); + const channelId = videoInfo.snippet.channelId; + const channelInfo = await getChannelInfo(apiKey, channelId); + + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + //console.log("Sending message to content.js"); + chrome.tabs.sendMessage(tabs[0].id, { + action: 'append-channel-info', + channelName: channelInfo.channelName, + uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), + videoCount: channelInfo.videoCount, + customUrl: channelInfo.customUrl + }); + }); + } catch (error) { + console.error(error); + } + }); + return true; + + } }); +async function getVideoInfo(apiKey, videoId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${apiKey}`); + const data = await response.json(); + return data.items[0]; +} + +async function getChannelInfo(apiKey, channelId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics&id=${channelId}&key=${apiKey}`); + const data = await response.json(); + const channel = data.items[0]; + + const channelName = channel.snippet.title; + const uploadTime = new Date(channel.snippet.publishedAt).toLocaleString(); + const videoCount = channel.statistics.videoCount; + const customUrl = channel.snippet.customUrl; + + return { channelName, uploadTime, videoCount, customUrl }; +} /*-----# UNINSTALL URL-----------------------------------*/ chrome.runtime.setUninstallURL('https://improvedtube.com/uninstalled'); diff --git a/js&css/extension/content.js b/js&css/extension/content.js new file mode 100644 index 000000000..5d824aaa1 --- /dev/null +++ b/js&css/extension/content.js @@ -0,0 +1,88 @@ +// Function to get the video ID from the URL +function getVideoIdFromUrl() { + const url = window.location.href; + const urlObj = new URL(url); + return urlObj.searchParams.get("v"); +} + +// Send the video ID to the background script +function sendVideoIdToBackground() { + const videoId = getVideoIdFromUrl(); + if (videoId) { + //console.log('Sending video ID to background:', videoId); + chrome.runtime.sendMessage({ action: 'store-video-id', videoId: videoId }, function(response) { + //console.log('Response from background:', response); + }); + } +} + +// Check if the switch is on and fetch new data. Otherwise, remove the details block +function checkSwitchStateAndFetchData() { + chrome.runtime.sendMessage({ action: 'check-switch-state' }, function(response) { + if (response.isSwitchOn) { + chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + } else { + // Remove existing video details if the switch is off + const targetElement = document.querySelector('.channel-info') + if (targetElement) { + targetElement.remove(); + } + } + }); +} + +document.addEventListener('DOMContentLoaded', function() { + + // After DOM is loaded, check the switch state and fetch data even if the video ID is the same + checkSwitchStateAndFetchData(); + sendVideoIdToBackground() + + let previousVideoId = getVideoIdFromUrl(); + + // Listen for changes in the video URL and send the updated video ID + const observer = new MutationObserver(() => { + const currentVideoId = getVideoIdFromUrl(); + if (currentVideoId !== previousVideoId) { + previousVideoId = currentVideoId; + + sendVideoIdToBackground(); + + checkSwitchStateAndFetchData(); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + + // Listen for visibility changes to handle page navigation + document.addEventListener('visibilitychange', function() { + if (document.visibilityState === 'visible') { + checkSwitchStateAndFetchData(); + } + }); + + // Listen for messages from the background.js script + chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { + if (message.action === 'append-channel-info') { + const {channelName, uploadTime, videoCount, customUrl} = message; + + // Forward the message to the web-accessible/www.youtube.com/channel.js script + window.postMessage( + { + type: 'CHANNEL_INFO', + channelName, + uploadTime, + videoCount, + customUrl, + switchState: true + }, + ); + } else if (message.action === 'remove-channel-info') { + // Forward the message to the web-accessible/www.youtube.com/channel.js script + window.postMessage( + { + type: 'CHANNEL_INFO', + switchState: false + }, + ); + } + }); +}) \ No newline at end of file diff --git a/js&css/extension/www.youtube.com/styles.css b/js&css/extension/www.youtube.com/styles.css index 3711aedeb..804c7af21 100644 --- a/js&css/extension/www.youtube.com/styles.css +++ b/js&css/extension/www.youtube.com/styles.css @@ -30,6 +30,7 @@ html {overflow-x: hidden !important} 6.0 Channel 6.1 "Play all" button 6.2 Featured content + 6.3 Details 7.0 Shortcuts 8.0 Settings 8.1 ImprovedTube icon on YouTube @@ -312,6 +313,45 @@ html[it-channel-hide-featured-content=true] #secondary ytd-browse-secondary-cont padding: 0; } +/*------------------------------------------------------------------------------ +6.3 Details +------------------------------------------------------------------------------*/ +.channel-info { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 2px; + align-items: center; + margin-right: 2px; +} +.channel-info .channel-name { + font-weight: bold; + display: block; + margin-bottom: 5px; +} +.channel-info .upload-time, .channel-info .video-count { + color: #555; + display: block; + margin-bottom: 5px; + width: 80px; + font-size: 13px; + font-weight: bold; +} +.channel-info .all-videos-link, +.channel-info .view-data-link { + font-family: 'Roboto', Arial, sans-serif; + font-size: 13px; + font-weight: bold; + border: none; + border-radius: 20px; + cursor: pointer; + padding: 8px 8px; + transition: background-color 0.3s ease, transform 0.2s ease; +} +.channel-info .all-videos-link:hover, +.channel-info .view-data-link:hover { + text-decoration: underline; +} /*------------------------------------------------------------------------------ 7.0 SHORTCUTS diff --git a/js&css/web-accessible/init.js b/js&css/web-accessible/init.js index ca6596ca6..013364405 100644 --- a/js&css/web-accessible/init.js +++ b/js&css/web-accessible/init.js @@ -94,6 +94,7 @@ ImprovedTube.init = function () { this.myColors(); this.YouTubeExperiments(); this.channelCompactTheme(); + this.channelDetails(); if (ImprovedTube.elements.player && ImprovedTube.elements.player.setPlaybackRate) { ImprovedTube.videoPageUpdate(); diff --git a/js&css/web-accessible/www.youtube.com/channel.js b/js&css/web-accessible/www.youtube.com/channel.js index a2fc34a42..6b2e12a47 100644 --- a/js&css/web-accessible/www.youtube.com/channel.js +++ b/js&css/web-accessible/www.youtube.com/channel.js @@ -144,3 +144,114 @@ ImprovedTube.channelCompactTheme = function () { compact.styles = [] } } + +/*------------------------------------------------------------------------------ +4.6.4 Details +------------------------------------------------------------------------------*/ +ImprovedTube.channelDetails = function () { + function createChannelInfo(channelName, uploadTime, videoCount, customUrl) { + //console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log + const container = document.createElement('div'); + container.className = 'channel-info'; + + const channelInfoContainer = document.createElement('div'); + + if (uploadTime) { + const uploadTimeElement = document.createElement('span'); + uploadTimeElement.className = 'upload-time'; + uploadTimeElement.textContent = `${uploadTime}`; + channelInfoContainer.appendChild(uploadTimeElement); + container.appendChild(channelInfoContainer); + } + if (videoCount) { + const videoCountElement = document.createElement('span'); + videoCountElement.className = 'video-count'; + videoCountElement.textContent = ` (${videoCount} videos)`; + channelInfoContainer.appendChild(videoCountElement); + container.appendChild(channelInfoContainer); + } + + // All Videos Link + const allVideosLink = document.createElement('a'); + allVideosLink.className = 'all-videos-link'; + allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; + allVideosLink.title = 'All Videos'; + + const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgElement.setAttribute('viewBox', '0 0 24 24'); + svgElement.setAttribute('width', '24px'); + svgElement.setAttribute('height', '24px'); + svgElement.setAttribute('fill', 'gray'); + container.appendChild(allVideosLink); + + const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + pathElement.setAttribute('d', 'M4 4h6v6H4V4zm0 10h6v6H4v-6zm10-10h6v6h-6V4zm0 10h6v6h-6v-6z'); + svgElement.appendChild(pathElement); + + allVideosLink.appendChild(svgElement); + container.appendChild(allVideosLink); + + // View Data Link + const viewDataLink = document.createElement('a'); + viewDataLink.className = 'view-data-link'; + viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; + // Tooltip text using `title` + viewDataLink.title = 'View detailed video data'; + + const svgElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgElement2.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgElement2.setAttribute('viewBox', '0 0 24 24'); + svgElement2.setAttribute('width', '24px'); + svgElement2.setAttribute('height', '24px'); + svgElement2.setAttribute('fill', 'gray'); + container.appendChild(viewDataLink); + + const pathElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + pathElement2.setAttribute('d', 'M11 7h2V5h-2v2zm1 14c-5.52 0-10-4.48-10-10S6.48 1 12 1s10 4.48 10 10-4.48 10-10 10zm-1-5h2v-6h-2v6z'); + svgElement2.appendChild(pathElement2); + + viewDataLink.appendChild(svgElement2); + container.appendChild(viewDataLink); + + // CSS for this Detail area is added in extension/styles.css + return container; + } + + document.addEventListener('DOMContentLoaded', function() { + // Listen for messages from the content.js script + window.addEventListener('message', (event) => { + // Only process messages with the expected type + if (event.source === window && event.data.type === 'CHANNEL_INFO') { + const { channelName, uploadTime, videoCount, customUrl, switchState } = event.data; + + const observer = new MutationObserver((mutationsList, observer) => { + const targetElement = document.querySelector('ytd-video-owner-renderer.style-scope.ytd-watch-metadata'); + + if (targetElement) { + // Stop observing once the target is found + observer.disconnect(); + + if(switchState) { + const channelInfo = createChannelInfo(channelName, uploadTime, videoCount, customUrl); + + // Removing existing channel info if present + const existingChannelInfo = targetElement.querySelector('.channel-info'); + if (existingChannelInfo) { + existingChannelInfo.remove(); + } + targetElement.appendChild(channelInfo); + } else { + targetElement.remove(); + } + } else { + console.log('Target element not found'); + } + }); + + // Start observing the body for child node changes + observer.observe(document.body, { childList: true, subtree: true }); + } + }); + }); +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 7080c728c..29a024d49 100644 --- a/manifest.json +++ b/manifest.json @@ -52,7 +52,8 @@ "js&css/extension/www.youtube.com/general/general.js", "js&css/extension/www.youtube.com/appearance/sidebar/sidebar.js", "js&css/extension/www.youtube.com/appearance/comments/comments.js", - "js&css/extension/init.js" + "js&css/extension/init.js", + "js&css/extension/content.js" ], "matches": [ "https://www.youtube.com/*" diff --git a/menu/skeleton-parts/channel.js b/menu/skeleton-parts/channel.js index 766b30d18..683296d0b 100644 --- a/menu/skeleton-parts/channel.js +++ b/menu/skeleton-parts/channel.js @@ -53,6 +53,42 @@ extension.skeleton.main.layers.section.channel = { channel_compact_theme: { component: 'switch', text: 'compactTheme' + }, + channel_details_button: { + component: 'switch', + text: 'Details', + value: false, // Default state is off + on: { + change: async function (event) { + const apiKey = YOUTUBE_API_KEY; + const switchElement = event.target.closest('.satus-switch'); + const isChecked = switchElement && switchElement.dataset.value === 'true'; + + // Store the switch state in chrome.storage.local + chrome.storage.local.set( { switchState: isChecked } ); + + try { + const videoId = await getCurrentVideoId(); + const videoInfo = await getVideoInfo(apiKey, videoId); + + const channelId = videoInfo.snippet.channelId + const channelInfo = await getChannelInfo(apiKey, channelId); + + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + //console.log("Sending message to content.js"); + chrome.tabs.sendMessage(tabs[0].id, { + action: isChecked ? 'append-channel-info' : 'remove-channel-info', + channelName: channelInfo.channelName, + uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), + videoCount: channelInfo.videoCount, + customUrl: channelInfo.customUrl + }) + }); + } catch (error){ + console.error(error); + } + } + } } } }, @@ -93,4 +129,37 @@ extension.skeleton.main.layers.section.channel = { component: 'span', text: 'channel' } -}; \ No newline at end of file +}; + +async function getCurrentVideoId() { + return new Promise((resolve, reject) => { + chrome.storage.local.get('videoId', (result) => { + //console.log('Retrieved video ID from storage:', result.videoId); // Debugging log + if (result.videoId) { + resolve(result.videoId); + } else { + reject('Video ID not found'); + } + }); + }); +} + +async function getVideoInfo(apiKey, videoId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); + const data = await response.json(); + const video = data?.items[0]; + + return video; +} + +async function getChannelInfo(apiKey, channelId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/channels?id=${channelId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); + const data = await response.json(); + const channel = data.items[0]; + const customUrl = channel.snippet.customUrl; + const channelName = channel.snippet.title; + const videoCount = channel.statistics.videoCount; + + return {channelName, videoCount, customUrl}; +} + diff --git a/package-lock.json b/package-lock.json index b183c3f50..3a154fe7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "devDependencies": { "@eslint/eslintrc": "latest", "@eslint/js": "latest", - "eslint": "^9.6.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.1", "globals": "^15.8.0", "jest": "^29.7.0", "jslint": "^0.12.1" @@ -646,20 +647,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -717,6 +704,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -752,13 +740,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "license": "Apache-2.0", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -774,18 +767,11 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1307,6 +1293,11 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1856,6 +1847,29 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.794", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.794.tgz", @@ -1906,37 +1920,41 @@ } }, "node_modules/eslint": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", - "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", - "license": "MIT", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.17.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.6.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -1950,10 +1968,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-plugin-compat": { @@ -2064,16 +2082,15 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", - "license": "BSD-2-Clause", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2091,6 +2108,41 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2103,6 +2155,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2119,6 +2198,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2149,6 +2253,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", @@ -2194,7 +2309,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2304,15 +2418,14 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "license": "MIT", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { @@ -2341,29 +2454,27 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "license": "MIT", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "license": "ISC" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==" }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2432,7 +2543,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2479,6 +2589,11 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2580,7 +2695,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2589,8 +2703,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -3408,8 +3521,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -3445,7 +3557,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -3654,7 +3765,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3812,7 +3922,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4071,6 +4180,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4483,8 +4607,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 202a25153..b4667666c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "devDependencies": { "@eslint/eslintrc": "latest", "@eslint/js": "latest", - "eslint": "^9.6.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.1", "globals": "^15.8.0", "jest": "^29.7.0", "jslint": "^0.12.1"