mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 03:27:03 +03:00
Compare commits
43 Commits
6.0.2
...
f92aad5ac2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92aad5ac2 | ||
|
|
a097ed7caf | ||
|
|
6c6263f0b1 | ||
|
|
18785d92ca | ||
|
|
726bc15bd7 | ||
|
|
2d3f7626ea | ||
|
|
44941acb38 | ||
|
|
8dbd566b13 | ||
|
|
145463ef2e | ||
|
|
8882b1d912 | ||
|
|
75288da07a | ||
|
|
eed5de9d41 | ||
|
|
dff3bdc2fa | ||
|
|
06ec260860 | ||
|
|
0a7fa754ff | ||
|
|
9f0a13830b | ||
|
|
4646f471bc | ||
|
|
60c4a0507c | ||
|
|
bd0be90135 | ||
|
|
590281f8eb | ||
|
|
df21d085e5 | ||
|
|
7a605b8a2c | ||
|
|
b59241cb08 | ||
|
|
03dc749440 | ||
|
|
f80ddc8841 | ||
|
|
479c31d57a | ||
|
|
f36d63e9bb | ||
|
|
510029af40 | ||
|
|
7749a12a0a | ||
|
|
0eb222ae05 | ||
|
|
1ae101405b | ||
|
|
f6109ace99 | ||
|
|
75caa40fe5 | ||
|
|
fa8db7c3b3 | ||
|
|
d165e06d2b | ||
|
|
7c61c8b44e | ||
|
|
ebc323a83b | ||
|
|
f64a552bf7 | ||
|
|
8d0d71b0f3 | ||
|
|
2a2d9de817 | ||
|
|
2004f6bf1b | ||
|
|
2370adb8b2 | ||
|
|
3d84152fa4 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "SponsorBlock",
|
||||
"version": "6.0.2",
|
||||
"version": "6.1.0",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_Description__",
|
||||
"homepage_url": "https://sponsor.ajay.app",
|
||||
|
||||
Submodule maze-utils updated: 04ddfb9be1...49dad0e67b
486
package-lock.json
generated
486
package-lock.json
generated
@@ -41,7 +41,7 @@
|
||||
"@types/wicg-mediasession": "^1.1.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"chromedriver": "^137.0.0",
|
||||
"chromedriver": "^140.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"eslint": "^8.35.0",
|
||||
@@ -757,9 +757,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/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==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -3072,31 +3072,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz",
|
||||
@@ -3535,6 +3521,20 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
@@ -3734,15 +3734,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chromedriver": {
|
||||
"version": "137.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-137.0.4.tgz",
|
||||
"integrity": "sha512-IxipIe0AnoQhWvG6eSTOLhVf1Tt586LA1IH4RdhGhXrrK6nZx8+VcKdjtlyZIV60S0CttUij/YnYuHAI+7qzWg==",
|
||||
"version": "140.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.4.tgz",
|
||||
"integrity": "sha512-/NUoxYBNkJeoNj1B5ux3KxGShITlxJctkbApgVAa3ZC8EvCLKaBclwU3/IEj5MJHnBJzqOVDxs/eTyaF9k2fOg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@testim/chrome-version": "^1.1.4",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.12.0",
|
||||
"compare-versions": "^6.1.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"proxy-agent": "^6.4.0",
|
||||
@@ -4577,6 +4577,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.15",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.15.tgz",
|
||||
@@ -4701,21 +4716,56 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
|
||||
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3",
|
||||
"has": "^1.0.3",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5148,9 +5198,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -5655,9 +5705,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5665,6 +5715,7 @@
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -5820,6 +5871,23 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
@@ -5860,9 +5928,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/function.prototype.name": {
|
||||
"version": "1.1.5",
|
||||
@@ -5969,14 +6042,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -5991,6 +6075,20 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -6173,12 +6271,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -6267,10 +6366,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -6279,12 +6379,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.2"
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6293,6 +6394,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
@@ -8816,9 +8930,9 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
@@ -8880,20 +8994,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
@@ -9172,6 +9272,16 @@
|
||||
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/memfs": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
|
||||
@@ -9337,9 +9447,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
@@ -13162,9 +13272,9 @@
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -14957,27 +15067,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
@@ -15284,6 +15381,16 @@
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
@@ -15417,13 +15524,13 @@
|
||||
}
|
||||
},
|
||||
"chromedriver": {
|
||||
"version": "137.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-137.0.4.tgz",
|
||||
"integrity": "sha512-IxipIe0AnoQhWvG6eSTOLhVf1Tt586LA1IH4RdhGhXrrK6nZx8+VcKdjtlyZIV60S0CttUij/YnYuHAI+7qzWg==",
|
||||
"version": "140.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.4.tgz",
|
||||
"integrity": "sha512-/NUoxYBNkJeoNj1B5ux3KxGShITlxJctkbApgVAa3ZC8EvCLKaBclwU3/IEj5MJHnBJzqOVDxs/eTyaF9k2fOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@testim/chrome-version": "^1.1.4",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.12.0",
|
||||
"compare-versions": "^6.1.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"proxy-agent": "^6.4.0",
|
||||
@@ -16033,6 +16140,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.5.15",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.15.tgz",
|
||||
@@ -16130,21 +16248,43 @@
|
||||
"which-typed-array": "^1.1.9"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
|
||||
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3",
|
||||
"has": "^1.0.3",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"es-shim-unscopables": {
|
||||
@@ -16355,9 +16495,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -16813,9 +16953,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"for-each": {
|
||||
@@ -16918,6 +17058,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
@@ -16948,8 +17101,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true
|
||||
},
|
||||
"function.prototype.name": {
|
||||
@@ -17029,14 +17183,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
@@ -17045,6 +17206,16 @@
|
||||
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -17168,13 +17339,10 @@
|
||||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
@@ -17241,18 +17409,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.2"
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
@@ -19079,9 +19256,9 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
@@ -19126,19 +19303,6 @@
|
||||
"whatwg-url": "^11.0.0",
|
||||
"ws": "^8.9.0",
|
||||
"xml-name-validator": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
@@ -19357,6 +19521,12 @@
|
||||
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true
|
||||
},
|
||||
"memfs": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
|
||||
@@ -19481,9 +19651,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-int64": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@types/wicg-mediasession": "^1.1.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"chromedriver": "^137.0.0",
|
||||
"chromedriver": "^140.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"eslint": "^8.35.0",
|
||||
|
||||
Submodule public/_locales updated: daa96aa537...d1f95394ca
@@ -25,7 +25,7 @@
|
||||
pointer-events: none;
|
||||
|
||||
height: 100%;
|
||||
transform: scaleY(0.6) translateY(-30%) translateY(1.5px);
|
||||
transform: scaleY(0.667) translateY(-30%) translateY(1.5px);
|
||||
z-index: 42;
|
||||
|
||||
transition: transform .1s cubic-bezier(0,0,0.2,1);
|
||||
|
||||
BIN
public/help/images/notice.png
Normal file
BIN
public/help/images/notice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
@@ -71,7 +71,7 @@
|
||||
__MSG_helpPageHowSkippingWorks2__
|
||||
</p>
|
||||
|
||||
<div class="center"><img src="images/voting on notice.gif"></div>
|
||||
<div class="center"><img src="images/notice.png"></div>
|
||||
|
||||
<p class="projectPreview">
|
||||
__MSG_helpPageHowSkippingWorks1__
|
||||
|
||||
@@ -321,6 +321,8 @@
|
||||
justify-content: space-evenly;
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
.sbControlsMenu-item {
|
||||
display: flex;
|
||||
|
||||
@@ -312,7 +312,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
</span>
|
||||
): ""}
|
||||
|
||||
{(!isNaN(segment[1]) && this.state.selectedActionType != ActionType.Full) ? (
|
||||
{(!isNaN(segment[0]) && this.state.selectedActionType != ActionType.Full) ? (
|
||||
<span id={"sponsorTimeInspectButton" + this.idSuffix}
|
||||
className="sponsorTimeEditButton"
|
||||
onClick={this.inspectTime.bind(this)}>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as React from "react";
|
||||
import * as CompileConfig from "../../../config.json";
|
||||
|
||||
import Config from "../../config";
|
||||
import { AdvancedSkipRuleSet, SkipRuleAttribute, SkipRuleOperator } from "../../utils/skipRule";
|
||||
import { ActionType, ActionTypes, CategorySkipOption } from "../../types";
|
||||
import { configToText, parseConfig, } from "../../utils/skipRule";
|
||||
import { AdvancedSkipRule } from "../../utils/skipRule.type";
|
||||
|
||||
let configSaveTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
@@ -64,210 +63,16 @@ export function AdvancedSkipOptionsComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
function compileConfig(config: string): AdvancedSkipRuleSet[] | null {
|
||||
const ruleSets: AdvancedSkipRuleSet[] = [];
|
||||
function compileConfig(config: string): AdvancedSkipRule[] | null {
|
||||
const { rules, errors } = parseConfig(config);
|
||||
|
||||
let ruleSet: AdvancedSkipRuleSet = {
|
||||
rules: [],
|
||||
skipOption: null,
|
||||
comment: ""
|
||||
};
|
||||
|
||||
for (const line of config.split("\n")) {
|
||||
if (line.trim().length === 0) {
|
||||
// Skip empty lines
|
||||
continue;
|
||||
}
|
||||
|
||||
const comment = line.match(/^\s*\/\/(.+)$/);
|
||||
if (comment) {
|
||||
if (ruleSet.rules.length > 0) {
|
||||
// Rule has already been created, add it to list if valid
|
||||
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
|
||||
ruleSets.push(ruleSet);
|
||||
|
||||
ruleSet = {
|
||||
rules: [],
|
||||
skipOption: null,
|
||||
comment: ""
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (ruleSet.comment.length > 0) {
|
||||
ruleSet.comment += "; ";
|
||||
}
|
||||
|
||||
ruleSet.comment += comment[1].trim();
|
||||
|
||||
// Skip comment lines
|
||||
continue;
|
||||
} else if (line.startsWith("if ")) {
|
||||
if (ruleSet.rules.length > 0) {
|
||||
// Rule has already been created, add it to list if valid
|
||||
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
|
||||
ruleSets.push(ruleSet);
|
||||
|
||||
ruleSet = {
|
||||
rules: [],
|
||||
skipOption: null,
|
||||
comment: ""
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const ruleTexts = [...line.matchAll(/\S+ \S+ (?:"[^"\\]*(?:\\.[^"\\]*)*"|\d+)(?= and |$)/g)];
|
||||
for (const ruleText of ruleTexts) {
|
||||
if (!ruleText[0]) return null;
|
||||
|
||||
const ruleParts = ruleText[0].match(/(\S+) (\S+) ("[^"\\]*(?:\\.[^"\\]*)*"|\d+)/);
|
||||
if (ruleParts.length !== 4) {
|
||||
return null; // Invalid rule format
|
||||
}
|
||||
|
||||
const attribute = getSkipRuleAttribute(ruleParts[1]);
|
||||
const operator = getSkipRuleOperator(ruleParts[2]);
|
||||
const value = getSkipRuleValue(ruleParts[3]);
|
||||
if (attribute === null || operator === null || value === null) {
|
||||
return null; // Invalid attribute or operator
|
||||
}
|
||||
|
||||
if ([SkipRuleOperator.Equal, SkipRuleOperator.NotEqual].includes(operator)) {
|
||||
if (attribute === SkipRuleAttribute.Category
|
||||
&& !CompileConfig.categoryList.includes(value as string)) {
|
||||
return null; // Invalid category value
|
||||
} else if (attribute === SkipRuleAttribute.ActionType
|
||||
&& !ActionTypes.includes(value as ActionType)) {
|
||||
return null; // Invalid category value
|
||||
} else if (attribute === SkipRuleAttribute.Source
|
||||
&& !["local", "youtube", "autogenerated", "server"].includes(value as string)) {
|
||||
return null; // Invalid category value
|
||||
}
|
||||
}
|
||||
|
||||
ruleSet.rules.push({
|
||||
attribute,
|
||||
operator,
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure all rules were parsed
|
||||
if (ruleTexts.length === 0 || !line.endsWith(ruleTexts[ruleTexts.length - 1][0])) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Only continue if a rule has been defined
|
||||
if (ruleSet.rules.length === 0) {
|
||||
return null; // No rules defined yet
|
||||
}
|
||||
|
||||
switch (line.trim().toLowerCase()) {
|
||||
case "disabled":
|
||||
ruleSet.skipOption = CategorySkipOption.Disabled;
|
||||
break;
|
||||
case "show overlay":
|
||||
ruleSet.skipOption = CategorySkipOption.ShowOverlay;
|
||||
break;
|
||||
case "manual skip":
|
||||
ruleSet.skipOption = CategorySkipOption.ManualSkip;
|
||||
break;
|
||||
case "auto skip":
|
||||
ruleSet.skipOption = CategorySkipOption.AutoSkip;
|
||||
break;
|
||||
default:
|
||||
return null; // Invalid skip option
|
||||
}
|
||||
}
|
||||
for (const error of errors) {
|
||||
console.error(`[SB] Error on line ${error.span.start.line}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (ruleSet.rules.length > 0 && ruleSet.skipOption !== null) {
|
||||
ruleSets.push(ruleSet);
|
||||
} else if (ruleSet.rules.length > 0 || ruleSet.skipOption !== null) {
|
||||
// Incomplete rule set
|
||||
return null;
|
||||
}
|
||||
|
||||
return ruleSets;
|
||||
}
|
||||
|
||||
function getSkipRuleAttribute(attribute: string): SkipRuleAttribute | null {
|
||||
if (attribute && Object.values(SkipRuleAttribute).includes(attribute as SkipRuleAttribute)) {
|
||||
return attribute as SkipRuleAttribute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSkipRuleOperator(operator: string): SkipRuleOperator | null {
|
||||
if (operator && Object.values(SkipRuleOperator).includes(operator as SkipRuleOperator)) {
|
||||
return operator as SkipRuleOperator;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSkipRuleValue(value: string): string | number | null {
|
||||
if (!value) return null;
|
||||
|
||||
if (value.startsWith('"')) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return null; // Invalid JSON string
|
||||
}
|
||||
if (errors.length === 0) {
|
||||
return rules;
|
||||
} else {
|
||||
const numValue = Number(value);
|
||||
if (!isNaN(numValue)) {
|
||||
return numValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function configToText(config: AdvancedSkipRuleSet[]): string {
|
||||
let result = "";
|
||||
|
||||
for (const ruleSet of config) {
|
||||
if (ruleSet.comment) {
|
||||
result += "// " + ruleSet.comment + "\n";
|
||||
}
|
||||
|
||||
result += "if ";
|
||||
let firstRule = true;
|
||||
for (const rule of ruleSet.rules) {
|
||||
if (!firstRule) {
|
||||
result += " and ";
|
||||
}
|
||||
|
||||
result += `${rule.attribute} ${rule.operator} ${JSON.stringify(rule.value)}`;
|
||||
firstRule = false;
|
||||
}
|
||||
|
||||
switch (ruleSet.skipOption) {
|
||||
case CategorySkipOption.Disabled:
|
||||
result += "\nDisabled";
|
||||
break;
|
||||
case CategorySkipOption.ShowOverlay:
|
||||
result += "\nShow Overlay";
|
||||
break;
|
||||
case CategorySkipOption.ManualSkip:
|
||||
result += "\nManual Skip";
|
||||
break;
|
||||
case CategorySkipOption.AutoSkip:
|
||||
result += "\nAuto Skip";
|
||||
break;
|
||||
default:
|
||||
return null; // Invalid skip option
|
||||
}
|
||||
|
||||
result += "\n\n";
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
@@ -98,6 +98,7 @@ export function CategoryChooserComponent() {
|
||||
id="configurationName"
|
||||
value={configurationName}
|
||||
placeholder={chrome.i18n.getMessage("ConfigurationName")}
|
||||
style={{width: document.getElementById("channelProfiles")?.clientWidth ?? null}}
|
||||
onChange={(e) => {
|
||||
const newName = e.target.value;
|
||||
getConfig(selectedConfigurationID)!.name = newName;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as CompileConfig from "../config.json";
|
||||
import * as invidiousList from "../ci/invidiouslist.json";
|
||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisibilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType, SegmentListDefaultTab } from "./types";
|
||||
import { Keybind, ProtoConfig, keybindEquals } from "../maze-utils/src/config";
|
||||
import { HashedValue } from "../maze-utils/src/hash";
|
||||
import { Permission, AdvancedSkipRuleSet } from "./utils/skipRule";
|
||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisibilityMode, PreviewBarOption, SponsorHideType, SponsorTime, VideoID, SegmentListDefaultTab } from "./types";
|
||||
import { Keybind, keybindEquals, ProtoConfig } from "../maze-utils/src/config";
|
||||
import type { HashedValue } from "../maze-utils/src/hash";
|
||||
import { AdvancedSkipCheck, AdvancedSkipPredicate, AdvancedSkipRule, Permission, PredicateOperator } from "./utils/skipRule.type";
|
||||
|
||||
interface SBConfig {
|
||||
userID: string;
|
||||
@@ -157,7 +157,7 @@ interface SBStorage {
|
||||
/* VideoID prefixes to UUID prefixes */
|
||||
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>;
|
||||
navigationApiAvailable: boolean;
|
||||
|
||||
|
||||
// Used when sync storage disabled
|
||||
alreadyInstalled: boolean;
|
||||
|
||||
@@ -168,7 +168,7 @@ interface SBStorage {
|
||||
skipProfileTemp: { time: number; configID: ConfigurationID } | null;
|
||||
skipProfiles: Record<ConfigurationID, CustomConfiguration>;
|
||||
|
||||
skipRules: AdvancedSkipRuleSet[];
|
||||
skipRules: AdvancedSkipRule[];
|
||||
}
|
||||
|
||||
class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
||||
@@ -188,6 +188,43 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
||||
}
|
||||
|
||||
function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
|
||||
if (local["skipRules"] && local["skipRules"].length !== 0 && local["skipRules"][0]["rules"]) {
|
||||
const output: AdvancedSkipRule[] = [];
|
||||
|
||||
for (const rule of local["skipRules"]) {
|
||||
const rules: object[] = rule["rules"];
|
||||
|
||||
if (rules.length !== 0) {
|
||||
let predicate: AdvancedSkipPredicate = {
|
||||
kind: "check",
|
||||
...rules[0] as AdvancedSkipCheck,
|
||||
};
|
||||
|
||||
for (let i = 1; i < rules.length; i++) {
|
||||
predicate = {
|
||||
kind: "operator",
|
||||
operator: PredicateOperator.And,
|
||||
left: predicate,
|
||||
right: {
|
||||
kind: "check",
|
||||
...rules[i] as AdvancedSkipCheck,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const comment = rule["comment"] as string;
|
||||
|
||||
output.push({
|
||||
predicate,
|
||||
skipOption: rule.skipOption,
|
||||
comments: comment.length === 0 ? [] : comment.split(/;\s*/),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
local["skipRules"] = output;
|
||||
}
|
||||
|
||||
if (config["whitelistedChannels"]) {
|
||||
// convert to skipProfiles
|
||||
const whitelistedChannels = config["whitelistedChannels"] as string[];
|
||||
@@ -214,7 +251,7 @@ function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
|
||||
for (const channelID of whitelistedChannels) {
|
||||
local.channelSkipProfileIDs[channelID] = skipProfileID;
|
||||
}
|
||||
local.channelSkipProfileIDs = local.channelSkipProfileIDs;
|
||||
local.channelSkipProfileIDs = local.channelSkipProfileIDs;
|
||||
|
||||
chrome.storage.sync.remove("whitelistedChannels");
|
||||
}
|
||||
@@ -248,7 +285,7 @@ function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
|
||||
name: "chapter" as Category,
|
||||
option: CategorySkipOption.ShowOverlay
|
||||
});
|
||||
|
||||
|
||||
config.categorySelections = config.categorySelections;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ function contentLocalConfigUpdateListener(changes: StorageChangesObject) {
|
||||
case "channelSkipProfileIDs":
|
||||
case "skipProfiles":
|
||||
case "skipProfileTemp":
|
||||
case "skipRules":
|
||||
channelIDChange();
|
||||
break;
|
||||
}
|
||||
@@ -1958,7 +1959,8 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
|
||||
return (!getSkipProfileBool("manualSkipOnFullVideo") || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||
&& (getCategorySelection(segment)?.option === CategorySkipOption.AutoSkip ||
|
||||
(getSkipProfileBool("autoSkipOnMusicVideos") && canSkipNonMusic && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
(getSkipProfileBool("autoSkipOnMusicVideos") && canSkipNonMusic
|
||||
&& sponsorTimes?.some((s) => s.category === "music_offtopic" && getCategorySelection(segment)?.option === CategorySkipOption.AutoSkip)
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment))
|
||||
|| isLoopedChapter(segment);
|
||||
@@ -1967,7 +1969,8 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
function shouldSkip(segment: SponsorTime): boolean {
|
||||
return segment.hidden === SponsorHideType.Visible && (segment.actionType !== ActionType.Full
|
||||
&& getCategorySelection(segment)?.option > CategorySkipOption.ShowOverlay)
|
||||
|| (getSkipProfileBool("autoSkipOnMusicVideos") && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
|| (getSkipProfileBool("autoSkipOnMusicVideos")
|
||||
&& sponsorTimes?.some((s) => s.category === "music_offtopic" && getCategorySelection(segment)?.option === CategorySkipOption.AutoSkip)
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| isLoopedChapter(segment);
|
||||
}
|
||||
@@ -2063,9 +2066,11 @@ function updateEditButtonsOnPlayer(): void {
|
||||
function getRealCurrentTime(): number {
|
||||
// Used to check if replay button
|
||||
const playButtonSVGData = document.querySelector(".ytp-play-button")?.querySelector(".ytp-svg-fill")?.getAttribute("d");
|
||||
const playButtonSVGDataNew = document.querySelector(".ytp-play-button")?.querySelector("path")?.getAttribute("d");
|
||||
const replaceSVGData = "M 18,11 V 7 l -5,5 5,5 v -4 c 3.3,0 6,2.7 6,6 0,3.3 -2.7,6 -6,6 -3.3,0 -6,-2.7 -6,-6 h -2 c 0,4.4 3.6,8 8,8 4.4,0 8,-3.6 8,-8 0,-4.4 -3.6,-8 -8,-8 z";
|
||||
const replaceSVGDataNew = "M11.29 2.92C14.85 1.33 18.87 1.06 22";
|
||||
|
||||
if (playButtonSVGData === replaceSVGData) {
|
||||
if (playButtonSVGData === replaceSVGData || playButtonSVGDataNew.startsWith(replaceSVGDataNew)) {
|
||||
// At the end of the video
|
||||
return getVideoDuration();
|
||||
} else {
|
||||
|
||||
@@ -957,6 +957,7 @@ class PreviewBar {
|
||||
const elem = document.createElement("div");
|
||||
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
|
||||
elem.classList.add("sponsorChapterText");
|
||||
elem.classList.add("ytp-chapter-title-content");
|
||||
if (document.location.host === "tv.youtube.com") {
|
||||
elem.style.lineHeight = "initial";
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import { getSkipProfileBool } from "./skipProfiles";
|
||||
|
||||
export function getControls(): HTMLElement {
|
||||
const controlsSelectors = [
|
||||
// New YouTube (2025 April)
|
||||
".ytp-right-controls-right",
|
||||
// YouTube
|
||||
".ytp-right-controls",
|
||||
// Mobile YouTube
|
||||
|
||||
@@ -1,63 +1,37 @@
|
||||
import { getCurrentPageTitle } from "../../maze-utils/src/elements";
|
||||
import { getChannelIDInfo, getVideoDuration } from "../../maze-utils/src/video";
|
||||
import Config from "../config";
|
||||
import { CategorySelection, CategorySkipOption, SponsorSourceType, SponsorTime } from "../types";
|
||||
import {ActionType, ActionTypes, CategorySelection, CategorySkipOption, SponsorSourceType, SponsorTime} from "../types";
|
||||
import { getSkipProfile, getSkipProfileBool } from "./skipProfiles";
|
||||
import { VideoLabelsCacheData } from "./videoLabels";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import { AdvancedSkipCheck, AdvancedSkipPredicate, AdvancedSkipRule, PredicateOperator, SkipRuleAttribute, SkipRuleOperator } from "./skipRule.type";
|
||||
|
||||
export interface Permission {
|
||||
canSubmit: boolean;
|
||||
}
|
||||
|
||||
export enum SkipRuleAttribute {
|
||||
StartTime = "time.start",
|
||||
EndTime = "time.end",
|
||||
Duration = "time.duration",
|
||||
StartTimePercent = "time.startPercent",
|
||||
EndTimePercent = "time.endPercent",
|
||||
DurationPercent = "time.durationPercent",
|
||||
Category = "category",
|
||||
ActionType = "actionType",
|
||||
Description = "chapter.name",
|
||||
Source = "chapter.source",
|
||||
ChannelID = "channel.id",
|
||||
ChannelName = "channel.name",
|
||||
VideoDuration = "video.duration",
|
||||
Title = "video.title"
|
||||
}
|
||||
|
||||
export enum SkipRuleOperator {
|
||||
Less = "<",
|
||||
LessOrEqual = "<=",
|
||||
Greater = ">",
|
||||
GreaterOrEqual = ">=",
|
||||
Equal = "==",
|
||||
NotEqual = "!=",
|
||||
Contains = "*=",
|
||||
NotContains = "!*=",
|
||||
Regex = "~=",
|
||||
RegexIgnoreCase = "~i=",
|
||||
NotRegex = "!~=",
|
||||
NotRegexIgnoreCase = "!~i="
|
||||
}
|
||||
|
||||
export interface AdvancedSkipRule {
|
||||
attribute: SkipRuleAttribute;
|
||||
operator: SkipRuleOperator;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface AdvancedSkipRuleSet {
|
||||
rules: AdvancedSkipRule[];
|
||||
skipOption: CategorySkipOption;
|
||||
comment: string;
|
||||
}
|
||||
const SKIP_RULE_ATTRIBUTES = Object.values(SkipRuleAttribute);
|
||||
const SKIP_RULE_OPERATORS = Object.values(SkipRuleOperator);
|
||||
const INVERTED_SKIP_RULE_OPERATORS = {
|
||||
"<=": SkipRuleOperator.Greater,
|
||||
"<": SkipRuleOperator.GreaterOrEqual,
|
||||
">=": SkipRuleOperator.Less,
|
||||
">": SkipRuleOperator.LessOrEqual,
|
||||
"!=": SkipRuleOperator.Equal,
|
||||
"==": SkipRuleOperator.NotEqual,
|
||||
"!*=": SkipRuleOperator.Contains,
|
||||
"*=": SkipRuleOperator.NotContains,
|
||||
"!~=": SkipRuleOperator.Regex,
|
||||
"~=": SkipRuleOperator.NotRegex,
|
||||
"!~i=": SkipRuleOperator.RegexIgnoreCase,
|
||||
"~i=": SkipRuleOperator.NotRegexIgnoreCase,
|
||||
};
|
||||
const WORD_EXTRA_CHARACTER = /[a-zA-Z0-9.]/;
|
||||
const OPERATOR_EXTRA_CHARACTER = /[<>=!~*&|-]/;
|
||||
const ANY_EXTRA_CHARACTER = /[a-zA-Z0-9<>=!~*&|.-]/;
|
||||
|
||||
export function getCategorySelection(segment: SponsorTime | VideoLabelsCacheData): CategorySelection {
|
||||
// First check skip rules
|
||||
for (const ruleSet of Config.local.skipRules) {
|
||||
if (ruleSet.rules.every((rule) => isSkipRulePassing(segment, rule))) {
|
||||
return { name: segment.category, option: ruleSet.skipOption } as CategorySelection;
|
||||
for (const rule of Config.local.skipRules) {
|
||||
if (isSkipPredicatePassing(segment, rule.predicate)) {
|
||||
return { name: segment.category, option: rule.skipOption } as CategorySelection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +58,7 @@ export function getCategorySelection(segment: SponsorTime | VideoLabelsCacheData
|
||||
return { name: segment.category, option: CategorySkipOption.Disabled} as CategorySelection;
|
||||
}
|
||||
|
||||
function getSkipRuleValue(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipRule): string | number | undefined {
|
||||
function getSkipCheckValue(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipCheck): string | number | undefined {
|
||||
switch (rule.attribute) {
|
||||
case SkipRuleAttribute.StartTime:
|
||||
return (segment as SponsorTime).segment?.[0];
|
||||
@@ -143,9 +117,9 @@ function getSkipRuleValue(segment: SponsorTime | VideoLabelsCacheData, rule: Adv
|
||||
}
|
||||
}
|
||||
|
||||
function isSkipRulePassing(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipRule): boolean {
|
||||
const value = getSkipRuleValue(segment, rule);
|
||||
|
||||
function isSkipCheckPassing(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipCheck): boolean {
|
||||
const value = getSkipCheckValue(segment, rule);
|
||||
|
||||
switch (rule.operator) {
|
||||
case SkipRuleOperator.Less:
|
||||
return typeof value === "number" && value < (rule.value as number);
|
||||
@@ -176,6 +150,19 @@ function isSkipRulePassing(segment: SponsorTime | VideoLabelsCacheData, rule: Ad
|
||||
}
|
||||
}
|
||||
|
||||
function isSkipPredicatePassing(segment: SponsorTime | VideoLabelsCacheData, predicate: AdvancedSkipPredicate): boolean {
|
||||
if (predicate.kind === "check") {
|
||||
return isSkipCheckPassing(segment, predicate as AdvancedSkipCheck);
|
||||
} else { // predicate.kind === "operator"
|
||||
// TODO Is recursion fine to use here?
|
||||
if (predicate.operator == PredicateOperator.And) {
|
||||
return isSkipPredicatePassing(segment, predicate.left) && isSkipPredicatePassing(segment, predicate.right);
|
||||
} else { // predicate.operator === PredicateOperator.Or
|
||||
return isSkipPredicatePassing(segment, predicate.left) || isSkipPredicatePassing(segment, predicate.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCategoryDefaultSelection(category: string): CategorySelection {
|
||||
for (const selection of Config.config.categorySelections) {
|
||||
if (selection.name === category) {
|
||||
@@ -183,4 +170,762 @@ export function getCategoryDefaultSelection(category: string): CategorySelection
|
||||
}
|
||||
}
|
||||
return { name: category, option: CategorySkipOption.Disabled} as CategorySelection;
|
||||
}
|
||||
}
|
||||
|
||||
type TokenType =
|
||||
| "if" // Keywords
|
||||
| "disabled" | "show overlay" | "manual skip" | "auto skip" // Skip option
|
||||
| `${SkipRuleAttribute}` // Segment attributes
|
||||
| `${SkipRuleOperator}` // Segment attribute operators
|
||||
| "and" | "or" | "not" // Expression operators
|
||||
| "(" | ")" | "comment" // Syntax
|
||||
| "string" | "number" // Literal values
|
||||
| "eof" | "error"; // Sentinel and special tokens
|
||||
|
||||
export interface SourcePos {
|
||||
line: number;
|
||||
}
|
||||
|
||||
export interface Span {
|
||||
start: SourcePos;
|
||||
end: SourcePos;
|
||||
}
|
||||
|
||||
interface Token {
|
||||
type: TokenType;
|
||||
span: Span;
|
||||
value: string;
|
||||
}
|
||||
|
||||
class Lexer {
|
||||
private readonly source: string;
|
||||
private start: number;
|
||||
private current: number;
|
||||
|
||||
private start_pos: SourcePos;
|
||||
private current_pos: SourcePos;
|
||||
|
||||
public constructor(source: string) {
|
||||
this.source = source;
|
||||
this.start = 0;
|
||||
this.current = 0;
|
||||
this.start_pos = { line: 1 };
|
||||
this.current_pos = { line: 1 };
|
||||
}
|
||||
|
||||
private makeToken(type: TokenType): Token {
|
||||
return {
|
||||
type,
|
||||
span: { start: this.start_pos, end: this.current_pos, },
|
||||
value: this.source.slice(this.start, this.current),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-16 value at the current position and advances it forward.
|
||||
* If the end of the source string has been reached, returns <code>null</code>.
|
||||
*
|
||||
* @return current UTF-16 value, or <code>null</code> on EOF
|
||||
*/
|
||||
private consume(): string | null {
|
||||
if (this.source.length > this.current) {
|
||||
// The UTF-16 value at the current position, which could be either a Unicode code point or a lone surrogate.
|
||||
// The check above this is also based on the UTF-16 value count, so this should not be able to fail on “weird” inputs.
|
||||
const c = this.source[this.current];
|
||||
this.current++;
|
||||
|
||||
if (c === "\n") {
|
||||
// Cannot use this.current_pos.line++, because SourcePos is mutable and used in tokens without copying
|
||||
this.current_pos = { line: this.current_pos.line + 1, };
|
||||
}
|
||||
|
||||
return c;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-16 value at the current position without advancing it.
|
||||
* If the end of the source string has been reached, returns <code>null</code>.
|
||||
*
|
||||
* @return current UTF-16 value, or <code>null</code> on EOF
|
||||
*/
|
||||
private peek(): string | null {
|
||||
if (this.source.length > this.current) {
|
||||
// See comment in consume() for Unicode expectations here
|
||||
return this.source[this.current];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the word at the current position against a list of
|
||||
* expected keywords. The keyword can consist of multiple characters.
|
||||
* If a match is found, the current position is advanced by the length
|
||||
* of the keyword found.
|
||||
*
|
||||
* @param keywords the expected set of keywords at the current position
|
||||
* @param caseSensitive whether to do a case-sensitive comparison
|
||||
* @return the matching keyword, or <code>null</code>
|
||||
*/
|
||||
private expectKeyword(keywords: readonly string[], caseSensitive: boolean): string | null {
|
||||
for (const keyword of keywords) {
|
||||
// slice() clamps to string length, so cannot cause out of bounds errors
|
||||
const actual = this.source.slice(this.current, this.current + keyword.length);
|
||||
|
||||
if (caseSensitive && keyword === actual || !caseSensitive && keyword.toLowerCase() === actual.toLowerCase()) {
|
||||
// Does not handle keywords containing line feeds, which shouldn't happen anyway
|
||||
this.current += keyword.length;
|
||||
return keyword;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips a series of whitespace characters starting at the current
|
||||
* position. May advance the current position multiple times, once,
|
||||
* or not at all.
|
||||
*/
|
||||
private skipWhitespace() {
|
||||
let c = this.peek();
|
||||
const whitespace = /\s+/;
|
||||
|
||||
while (c != null) {
|
||||
if (!whitespace.test(c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.consume();
|
||||
c = this.peek();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips all characters until the next <code>"\n"</code> (line feed)
|
||||
* character occurs (inclusive). Will always advance the current position
|
||||
* at least once.
|
||||
*/
|
||||
private skipLine() {
|
||||
let c = this.consume();
|
||||
while (c != null) {
|
||||
if (c == '\n') {
|
||||
return;
|
||||
}
|
||||
|
||||
c = this.consume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the lexer has reached the end of input
|
||||
*/
|
||||
private isEof(): boolean {
|
||||
return this.current >= this.source.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start position of the next token that will be emitted
|
||||
* to the current position.
|
||||
*
|
||||
* More characters need to be consumed after calling this, as
|
||||
* an empty token would be emitted otherwise.
|
||||
*/
|
||||
private resetToCurrent() {
|
||||
this.start = this.current;
|
||||
this.start_pos = this.current_pos;
|
||||
}
|
||||
|
||||
public nextToken(): Token {
|
||||
this.skipWhitespace();
|
||||
this.resetToCurrent();
|
||||
|
||||
if (this.isEof()) {
|
||||
return this.makeToken("eof");
|
||||
}
|
||||
|
||||
const keyword = this.expectKeyword([
|
||||
"if", "and", "or", "not",
|
||||
"(", ")",
|
||||
"//",
|
||||
].concat(SKIP_RULE_ATTRIBUTES)
|
||||
.concat(SKIP_RULE_OPERATORS), true);
|
||||
let type: TokenType | null = null;
|
||||
let kind: "word" | "operator" | null = null;
|
||||
|
||||
if (keyword !== null) {
|
||||
if ((SKIP_RULE_ATTRIBUTES as string[]).includes(keyword)) {
|
||||
kind = "word";
|
||||
type = keyword as TokenType;
|
||||
} else if ((SKIP_RULE_OPERATORS as string[]).includes(keyword)) {
|
||||
kind = "operator";
|
||||
type = keyword as TokenType;
|
||||
} else {
|
||||
switch (keyword) {
|
||||
case "if": // Fallthrough
|
||||
case "and": // Fallthrough
|
||||
case "or": // Fallthrough
|
||||
case "not": kind = "word"; type = keyword as TokenType; break;
|
||||
|
||||
case "(": return this.makeToken("(");
|
||||
case ")": return this.makeToken(")");
|
||||
|
||||
case "//":
|
||||
this.resetToCurrent();
|
||||
this.skipLine();
|
||||
return this.makeToken("comment");
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const keyword2 = this.expectKeyword(
|
||||
[ "disabled", "show overlay", "manual skip", "auto skip" ], false);
|
||||
|
||||
if (keyword2 !== null) {
|
||||
kind = "word";
|
||||
type = keyword2 as TokenType;
|
||||
}
|
||||
}
|
||||
|
||||
if (type !== null) {
|
||||
const more = kind == "operator" ? OPERATOR_EXTRA_CHARACTER : kind == "word" ? WORD_EXTRA_CHARACTER : ANY_EXTRA_CHARACTER;
|
||||
|
||||
let c = this.peek();
|
||||
let error = false;
|
||||
while (c !== null && more.test(c)) {
|
||||
error = true;
|
||||
this.consume();
|
||||
c = this.peek();
|
||||
}
|
||||
|
||||
return this.makeToken(error ? "error" : type);
|
||||
}
|
||||
|
||||
let c = this.consume();
|
||||
|
||||
if (c === '"') {
|
||||
// Parses string according to ECMA-404 2nd edition (JSON), section 9 “String”
|
||||
let output = "";
|
||||
let c = this.consume();
|
||||
let error = false;
|
||||
|
||||
while (c !== null && c !== '"') {
|
||||
if (c == '\\') {
|
||||
c = this.consume();
|
||||
|
||||
switch (c) {
|
||||
case '"':
|
||||
output = output.concat('"');
|
||||
break;
|
||||
case '\\':
|
||||
output = output.concat('\\');
|
||||
break;
|
||||
case '/':
|
||||
output = output.concat('/');
|
||||
break;
|
||||
case 'b':
|
||||
output = output.concat('\b');
|
||||
break;
|
||||
case 'f':
|
||||
output = output.concat('\f');
|
||||
break;
|
||||
case 'n':
|
||||
output = output.concat('\n');
|
||||
break;
|
||||
case 'r':
|
||||
output = output.concat('\r');
|
||||
break;
|
||||
case 't':
|
||||
output = output.concat('\t');
|
||||
break;
|
||||
case 'u': {
|
||||
// UTF-16 value sequence
|
||||
const digits = this.source.slice(this.current, this.current + 4);
|
||||
|
||||
if (digits.length < 4 || !/[0-9a-zA-Z]{4}/.test(digits)) {
|
||||
error = true;
|
||||
output = output.concat(`\\u`);
|
||||
c = this.consume();
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = parseInt(digits, 16);
|
||||
// fromCharCode() takes a UTF-16 value without performing validity checks,
|
||||
// which is exactly what is needed here – in JSON, code units outside the
|
||||
// BMP are represented by two Unicode escape sequences.
|
||||
output = output.concat(String.fromCharCode(value));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error = true;
|
||||
output = output.concat(`\\${c}`);
|
||||
break;
|
||||
}
|
||||
} else if (c === '\n') {
|
||||
// Unterminated / multi-line string, unsupported
|
||||
error = true;
|
||||
// Prevent unterminated strings from consuming the entire rest of the input
|
||||
break;
|
||||
} else {
|
||||
output = output.concat(c);
|
||||
}
|
||||
|
||||
c = this.consume();
|
||||
}
|
||||
|
||||
return {
|
||||
type: error || c !== '"' ? "error" : "string",
|
||||
span: { start: this.start_pos, end: this.current_pos, },
|
||||
value: output,
|
||||
};
|
||||
} else if (/[0-9-]/.test(c)) {
|
||||
// Parses number according to ECMA-404 2nd edition (JSON), section 8 “Numbers”
|
||||
if (c === '-') {
|
||||
c = this.consume();
|
||||
|
||||
if (!/[0-9]/.test(c)) {
|
||||
return this.makeToken("error");
|
||||
}
|
||||
}
|
||||
|
||||
const leadingZero = c === '0';
|
||||
let next = this.peek();
|
||||
let error = false;
|
||||
|
||||
while (next !== null && /[0-9]/.test(next)) {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
|
||||
if (leadingZero) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (next !== null && next === '.') {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
|
||||
if (next === null || !/[0-9]/.test(next)) {
|
||||
return this.makeToken("error");
|
||||
}
|
||||
|
||||
do {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
} while (next !== null && /[0-9]/.test(next));
|
||||
}
|
||||
|
||||
next = this.peek();
|
||||
|
||||
if (next != null && (next === 'e' || next === 'E')) {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
|
||||
if (next === null) {
|
||||
return this.makeToken("error");
|
||||
}
|
||||
|
||||
if (next === '+' || next === '-') {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
}
|
||||
|
||||
while (next !== null && /[0-9]/.test(next)) {
|
||||
this.consume();
|
||||
next = this.peek();
|
||||
}
|
||||
}
|
||||
|
||||
return this.makeToken(error ? "error" : "number");
|
||||
}
|
||||
|
||||
// Consume common characters up to a space for a more useful value in the error token
|
||||
const common = ANY_EXTRA_CHARACTER;
|
||||
c = this.peek();
|
||||
while (c !== null && common.test(c)) {
|
||||
this.consume();
|
||||
c = this.peek();
|
||||
}
|
||||
|
||||
return this.makeToken("error");
|
||||
}
|
||||
}
|
||||
|
||||
export interface ParseError {
|
||||
span: Span;
|
||||
message: string;
|
||||
}
|
||||
|
||||
class Parser {
|
||||
private lexer: Lexer;
|
||||
|
||||
private previous: Token;
|
||||
private current: Token;
|
||||
|
||||
private readonly rules: AdvancedSkipRule[];
|
||||
private readonly errors: ParseError[];
|
||||
|
||||
private erroring: boolean;
|
||||
private panicMode: boolean;
|
||||
|
||||
public constructor(lexer: Lexer) {
|
||||
this.lexer = lexer;
|
||||
this.previous = null;
|
||||
this.current = lexer.nextToken();
|
||||
this.rules = [];
|
||||
this.errors = [];
|
||||
this.erroring = false;
|
||||
this.panicMode = false;
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
/**
|
||||
* Adds an error message. The current skip rule will be marked as erroring.
|
||||
*
|
||||
* @param span the range of the error
|
||||
* @param message the message to report
|
||||
* @param panic if <code>true</code>, all further errors will be silenced
|
||||
* until panic mode is disabled again
|
||||
*/
|
||||
private errorAt(span: Span, message: string, panic: boolean) {
|
||||
if (!this.panicMode) {
|
||||
this.errors.push({span, message,});
|
||||
}
|
||||
|
||||
this.panicMode ||= panic;
|
||||
this.erroring = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error message for an error occurring at the previous token
|
||||
* (which was just consumed).
|
||||
*
|
||||
* @param message the message to report
|
||||
* @param panic if <code>true</code>, all further errors will be silenced
|
||||
* until panic mode is disabled again
|
||||
*/
|
||||
private error(message: string, panic: boolean) {
|
||||
this.errorAt(this.previous.span, message, panic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error message for an error occurring at the current token
|
||||
* (which has not been consumed yet).
|
||||
*
|
||||
* @param message the message to report
|
||||
* @param panic if <code>true</code>, all further errors will be silenced
|
||||
* until panic mode is disabled again
|
||||
*/
|
||||
private errorAtCurrent(message: string, panic: boolean) {
|
||||
this.errorAt(this.current.span, message, panic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the current token, which can then be accessed at <code>previous</code>.
|
||||
* The next token will be at <code>current</code> after this call.
|
||||
*
|
||||
* If a token of type <code>error</code> is found, issues an error message.
|
||||
*/
|
||||
private consume() {
|
||||
this.previous = this.current;
|
||||
// Intentionally ignoring `error` tokens here;
|
||||
// by handling those in later privates with more context (match(), expect(), ...),
|
||||
// the user gets better errors
|
||||
this.current = this.lexer.nextToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current token (that has not been consumed yet) against a set of expected token types.
|
||||
*
|
||||
* @param expected the set of expected token types
|
||||
* @return whether the actual current token matches any expected token type
|
||||
*/
|
||||
private match(expected: readonly TokenType[]): boolean {
|
||||
if (expected.includes(this.current.type)) {
|
||||
this.consume();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current token (that has not been consumed yet) against a set of expected token types.
|
||||
*
|
||||
* If there is no match, issues an error message which will be prepended to <code>, got: <token type></code>.
|
||||
*
|
||||
* @param expected the set of expected token types
|
||||
* @param message the error message to report in case the actual token doesn't match
|
||||
* @param panic if <code>true</code>, all further errors will be silenced
|
||||
* until panic mode is disabled again
|
||||
*/
|
||||
private expect(expected: readonly TokenType[], message: string, panic: boolean) {
|
||||
if (!this.match(expected)) {
|
||||
this.errorAtCurrent(message.concat(this.current.type === "error" ? `, got: ${JSON.stringify(this.current.value)}` : `, got: \`${this.current.type}\``), panic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize with the next rule block and disable panic mode.
|
||||
* Skips all tokens until the <code>if</code> keyword is found.
|
||||
*/
|
||||
private synchronize() {
|
||||
this.panicMode = false;
|
||||
|
||||
while (!this.isEof()) {
|
||||
if (this.current.type === "if") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.consume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the parser has reached the end of input
|
||||
*/
|
||||
private isEof(): boolean {
|
||||
return this.current.type === "eof";
|
||||
}
|
||||
|
||||
// Parsing functions
|
||||
|
||||
/**
|
||||
* Parse the config. Should only ever be called once on a given
|
||||
* <code>Parser</code> instance.
|
||||
*/
|
||||
public parse(): { rules: AdvancedSkipRule[]; errors: ParseError[] } {
|
||||
while (!this.isEof()) {
|
||||
this.erroring = false;
|
||||
const rule = this.parseRule();
|
||||
|
||||
if (!this.erroring && rule) {
|
||||
this.rules.push(rule);
|
||||
}
|
||||
|
||||
if (this.panicMode) {
|
||||
this.synchronize();
|
||||
}
|
||||
}
|
||||
|
||||
return { rules: this.rules, errors: this.errors, };
|
||||
}
|
||||
|
||||
private parseRule(): AdvancedSkipRule | null {
|
||||
const rule: AdvancedSkipRule = {
|
||||
predicate: null,
|
||||
skipOption: null,
|
||||
comments: [],
|
||||
};
|
||||
|
||||
while (this.match(["comment"])) {
|
||||
rule.comments.push(this.previous.value.trim());
|
||||
}
|
||||
|
||||
this.expect(["if"], rule.comments.length !== 0 ? "expected `if` after `comment`" : "expected `if`", true);
|
||||
rule.predicate = this.parsePredicate();
|
||||
|
||||
this.expect(["disabled", "show overlay", "manual skip", "auto skip"], "expected skip option after condition", true);
|
||||
switch (this.previous.type) {
|
||||
case "disabled":
|
||||
rule.skipOption = CategorySkipOption.Disabled;
|
||||
break;
|
||||
case "show overlay":
|
||||
rule.skipOption = CategorySkipOption.ShowOverlay;
|
||||
break;
|
||||
case "manual skip":
|
||||
rule.skipOption = CategorySkipOption.ManualSkip;
|
||||
break;
|
||||
case "auto skip":
|
||||
rule.skipOption = CategorySkipOption.AutoSkip;
|
||||
break;
|
||||
default:
|
||||
// Ignore, should have already errored
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
private parsePredicate(): AdvancedSkipPredicate | null {
|
||||
return this.parseOr();
|
||||
}
|
||||
|
||||
private parseOr(): AdvancedSkipPredicate | null {
|
||||
let left = this.parseAnd();
|
||||
|
||||
while (this.match(["or"])) {
|
||||
const right = this.parseAnd();
|
||||
|
||||
left = {
|
||||
kind: "operator",
|
||||
operator: PredicateOperator.Or,
|
||||
left, right,
|
||||
};
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
private parseAnd(): AdvancedSkipPredicate | null {
|
||||
let left = this.parseUnary();
|
||||
|
||||
while (this.match(["and"])) {
|
||||
const right = this.parseUnary();
|
||||
|
||||
left = {
|
||||
kind: "operator",
|
||||
operator: PredicateOperator.And,
|
||||
left, right,
|
||||
};
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
private parseUnary(): AdvancedSkipPredicate | null {
|
||||
if (this.match(["not"])) {
|
||||
const predicate = this.parseUnary();
|
||||
return predicate ? invertPredicate(predicate) : null;
|
||||
}
|
||||
|
||||
return this.parsePrimary();
|
||||
}
|
||||
|
||||
private parsePrimary(): AdvancedSkipPredicate | null {
|
||||
if (this.match(["("])) {
|
||||
const predicate = this.parsePredicate();
|
||||
this.expect([")"], "expected `)` after condition", true);
|
||||
return predicate;
|
||||
} else {
|
||||
return this.parseCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private parseCheck(): AdvancedSkipCheck | null {
|
||||
this.expect(SKIP_RULE_ATTRIBUTES, `expected attribute after \`${this.previous.type}\``, true);
|
||||
|
||||
if (this.erroring) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attribute = this.previous.type as SkipRuleAttribute;
|
||||
this.expect(SKIP_RULE_OPERATORS, `expected operator after \`${attribute}\``, true);
|
||||
|
||||
if (this.erroring) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const operator = this.previous.type as SkipRuleOperator;
|
||||
this.expect(["string", "number"], `expected string or number after \`${operator}\``, true);
|
||||
|
||||
if (this.erroring) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = this.previous.type === "number" ? Number(this.previous.value) : this.previous.value;
|
||||
|
||||
if ([SkipRuleOperator.Equal, SkipRuleOperator.NotEqual].includes(operator)) {
|
||||
if (attribute === SkipRuleAttribute.Category
|
||||
&& !CompileConfig.categoryList.includes(value as string)) {
|
||||
this.error(`unknown category: \`${value}\``, false);
|
||||
return null;
|
||||
} else if (attribute === SkipRuleAttribute.ActionType
|
||||
&& !ActionTypes.includes(value as ActionType)) {
|
||||
this.error(`unknown action type: \`${value}\``, false);
|
||||
return null;
|
||||
} else if (attribute === SkipRuleAttribute.Source
|
||||
&& !["local", "youtube", "autogenerated", "server"].includes(value as string)) {
|
||||
this.error(`unknown chapter source: \`${value}\``, false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "check",
|
||||
attribute, operator, value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function parseConfig(config: string): { rules: AdvancedSkipRule[]; errors: ParseError[] } {
|
||||
const parser = new Parser(new Lexer(config));
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
export function configToText(config: AdvancedSkipRule[]): string {
|
||||
let result = "";
|
||||
|
||||
for (const rule of config) {
|
||||
for (const comment of rule.comments) {
|
||||
result += "// " + comment + "\n";
|
||||
}
|
||||
|
||||
result += "if ";
|
||||
result += predicateToText(rule.predicate, null);
|
||||
|
||||
switch (rule.skipOption) {
|
||||
case CategorySkipOption.Disabled:
|
||||
result += "\nDisabled";
|
||||
break;
|
||||
case CategorySkipOption.ShowOverlay:
|
||||
result += "\nShow Overlay";
|
||||
break;
|
||||
case CategorySkipOption.ManualSkip:
|
||||
result += "\nManual Skip";
|
||||
break;
|
||||
case CategorySkipOption.AutoSkip:
|
||||
result += "\nAuto Skip";
|
||||
break;
|
||||
default:
|
||||
return null; // Invalid skip option
|
||||
}
|
||||
|
||||
result += "\n\n";
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
function predicateToText(predicate: AdvancedSkipPredicate, outerPrecedence: "or" | "and" | "not" | null): string {
|
||||
if (predicate.kind === "check") {
|
||||
return `${predicate.attribute} ${predicate.operator} ${JSON.stringify(predicate.value)}`;
|
||||
} else if (predicate.displayInverted) {
|
||||
// Should always be fine, considering `not` has the highest precedence
|
||||
return `not ${predicateToText(invertPredicate(predicate), "not")}`;
|
||||
} else {
|
||||
let text: string;
|
||||
|
||||
if (predicate.operator === PredicateOperator.And) {
|
||||
text = `${predicateToText(predicate.left, "and")} and ${predicateToText(predicate.right, "and")}`;
|
||||
} else { // Or
|
||||
text = `${predicateToText(predicate.left, "or")} or ${predicateToText(predicate.right, "or")}`;
|
||||
}
|
||||
|
||||
return outerPrecedence !== null && outerPrecedence !== predicate.operator ? `(${text})` : text;
|
||||
}
|
||||
}
|
||||
|
||||
function invertPredicate(predicate: AdvancedSkipPredicate): AdvancedSkipPredicate {
|
||||
if (predicate.kind === "check") {
|
||||
return {
|
||||
...predicate,
|
||||
operator: INVERTED_SKIP_RULE_OPERATORS[predicate.operator],
|
||||
};
|
||||
} else {
|
||||
// not (a and b) == (not a or not b)
|
||||
// not (a or b) == (not a and not b)
|
||||
return {
|
||||
kind: "operator",
|
||||
operator: predicate.operator === "and" ? PredicateOperator.Or : PredicateOperator.And,
|
||||
left: predicate.left ? invertPredicate(predicate.left) : null,
|
||||
right: predicate.right ? invertPredicate(predicate.right) : null,
|
||||
displayInverted: !predicate.displayInverted,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
71
src/utils/skipRule.type.ts
Normal file
71
src/utils/skipRule.type.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { CategorySkipOption } from "../types";
|
||||
|
||||
export interface Permission {
|
||||
canSubmit: boolean;
|
||||
}
|
||||
|
||||
// Note that attributes that are prefixes of other attributes (like `time.start`) need to be ordered *after*
|
||||
// the longer attributes, because these are matched sequentially. Using the longer attribute would otherwise result
|
||||
// in an error token.
|
||||
export enum SkipRuleAttribute {
|
||||
StartTimePercent = "time.startPercent",
|
||||
StartTime = "time.start",
|
||||
EndTimePercent = "time.endPercent",
|
||||
EndTime = "time.end",
|
||||
DurationPercent = "time.durationPercent",
|
||||
Duration = "time.duration",
|
||||
Category = "category",
|
||||
ActionType = "actionType",
|
||||
Description = "chapter.name",
|
||||
Source = "chapter.source",
|
||||
ChannelID = "channel.id",
|
||||
ChannelName = "channel.name",
|
||||
VideoDuration = "video.duration",
|
||||
Title = "video.title"
|
||||
}
|
||||
|
||||
// Note that operators that are prefixes of other attributes (like `<`) need to be ordered *after* the longer
|
||||
// operators, because these are matched sequentially. Using the longer operator would otherwise result
|
||||
// in an error token.
|
||||
export enum SkipRuleOperator {
|
||||
LessOrEqual = "<=",
|
||||
Less = "<",
|
||||
GreaterOrEqual = ">=",
|
||||
Greater = ">",
|
||||
NotEqual = "!=",
|
||||
Equal = "==",
|
||||
NotContains = "!*=",
|
||||
Contains = "*=",
|
||||
NotRegex = "!~=",
|
||||
Regex = "~=",
|
||||
NotRegexIgnoreCase = "!~i=",
|
||||
RegexIgnoreCase = "~i="
|
||||
}
|
||||
|
||||
export interface AdvancedSkipCheck {
|
||||
kind: "check";
|
||||
attribute: SkipRuleAttribute;
|
||||
operator: SkipRuleOperator;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export enum PredicateOperator {
|
||||
And = "and",
|
||||
Or = "or",
|
||||
}
|
||||
|
||||
export interface AdvancedSkipOperator {
|
||||
kind: "operator";
|
||||
operator: PredicateOperator;
|
||||
left: AdvancedSkipPredicate;
|
||||
right: AdvancedSkipPredicate;
|
||||
displayInverted?: boolean;
|
||||
}
|
||||
|
||||
export type AdvancedSkipPredicate = AdvancedSkipCheck | AdvancedSkipOperator;
|
||||
|
||||
export interface AdvancedSkipRule {
|
||||
predicate: AdvancedSkipPredicate;
|
||||
skipOption: CategorySkipOption;
|
||||
comments: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user