Add rule for eipsode count file mismatch (#75)

* Added rule for episode count mismatch

Close #70

* Updated deps
This commit is contained in:
TheLegendTubaGuy
2025-11-10 16:12:00 -06:00
committed by GitHub
parent da36319c89
commit 582f73b549
9 changed files with 180 additions and 108 deletions

View File

@@ -12,6 +12,8 @@ BLOCK_REMOVED_NO_FILES_RELEASES=false
REMOVE_NOT_AN_UPGRADE=false
REMOVE_SERIES_ID_MISMATCH=false
BLOCK_REMOVED_SERIES_ID_MISMATCH_RELEASES=false
REMOVE_EPISODE_COUNT_MISMATCH=false
BLOCK_REMOVED_EPISODE_COUNT_MISMATCH_RELEASES=false
REMOVE_UNDETERMINED_SAMPLE=false
BLOCK_REMOVED_UNDETERMIND_SAMPLE=false

View File

@@ -28,6 +28,8 @@ Automated queue cleaner for Sonarr that removes stuck downloads based on configu
| `REMOVE_NOT_AN_UPGRADE` | `false` | Remove items flagged as "Not an upgrade" |
| `REMOVE_SERIES_ID_MISMATCH` | `false` | Remove items with series ID matching conflicts |
| `BLOCK_REMOVED_SERIES_ID_MISMATCH_RELEASES` | `false` | Add series ID mismatch items to blocklist |
| `REMOVE_EPISODE_COUNT_MISMATCH` | `false` | Remove items where the on-disk file spans more episodes than the release |
| `BLOCK_REMOVED_EPISODE_COUNT_MISMATCH_RELEASES` | `false` | Add episode-count mismatch items to blocklist |
| `REMOVE_UNDETERMINED_SAMPLE` | `false` | Remove items unable to determine if file is a sample |
| `BLOCK_REMOVED_UNDETERMINED_SAMPLE` | `false` | Add undetermined sample items to blocklist |
| `DRY_RUN` | `false` | Log actions without actually removing/blocking items |
@@ -100,6 +102,8 @@ services:
REMOVE_NOT_AN_UPGRADE: 'true'
REMOVE_SERIES_ID_MISMATCH: 'true'
BLOCK_REMOVED_SERIES_ID_MISMATCH_RELEASES: 'false'
REMOVE_EPISODE_COUNT_MISMATCH: 'false'
BLOCK_REMOVED_EPISODE_COUNT_MISMATCH_RELEASES: 'false'
REMOVE_UNDETERMINED_SAMPLE: 'false'
BLOCK_REMOVED_UNDETERMINED_SAMPLE: 'false'
DRY_RUN: 'false'

View File

@@ -13,8 +13,8 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"axios": "^1.13.1",
"cron": "^4.3.3",
"axios": "^1.13.2",
"cron": "^4.3.4",
"dotenv": "^17.2.3",
"yaml": "^2.8.1"
},
@@ -22,14 +22,14 @@
"@eslint/js": "^9.39.1",
"@types/jest": "^30.0.0",
"@types/node": "^24.10.0",
"@typescript-eslint/eslint-plugin": "^8.46.3",
"@typescript-eslint/parser": "^8.46.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^9.39.1",
"jest": "^30.2.0",
"ts-jest": "^29.4.5",
"tsx": "^4.20.6",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3"
"typescript-eslint": "^8.46.4"
},
"engines": {
"node": ">=22"

202
pnpm-lock.yaml generated
View File

@@ -9,11 +9,11 @@ importers:
.:
dependencies:
axios:
specifier: ^1.13.1
version: 1.13.1
specifier: ^1.13.2
version: 1.13.2
cron:
specifier: ^4.3.3
version: 4.3.3
specifier: ^4.3.4
version: 4.3.4
dotenv:
specifier: ^17.2.3
version: 17.2.3
@@ -31,11 +31,11 @@ importers:
specifier: ^24.10.0
version: 24.10.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.46.3
version: 8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
specifier: ^8.46.4
version: 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: ^8.46.3
version: 8.46.3(eslint@9.39.1)(typescript@5.9.3)
specifier: ^8.46.4
version: 8.46.4(eslint@9.39.1)(typescript@5.9.3)
eslint:
specifier: ^9.39.1
version: 9.39.1
@@ -52,8 +52,8 @@ importers:
specifier: ^5.9.3
version: 5.9.3
typescript-eslint:
specifier: ^8.46.3
version: 8.46.3(eslint@9.39.1)(typescript@5.9.3)
specifier: ^8.46.4
version: 8.46.4(eslint@9.39.1)(typescript@5.9.3)
packages:
@@ -631,63 +631,63 @@ packages:
'@types/yargs@17.0.34':
resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==}
'@typescript-eslint/eslint-plugin@8.46.3':
resolution: {integrity: sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==}
'@typescript-eslint/eslint-plugin@8.46.4':
resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.46.3
'@typescript-eslint/parser': ^8.46.4
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/parser@8.46.3':
resolution: {integrity: sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==}
'@typescript-eslint/parser@8.46.4':
resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.46.3':
resolution: {integrity: sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==}
'@typescript-eslint/project-service@8.46.4':
resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/scope-manager@8.46.3':
resolution: {integrity: sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==}
'@typescript-eslint/scope-manager@8.46.4':
resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.46.3':
resolution: {integrity: sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==}
'@typescript-eslint/tsconfig-utils@8.46.4':
resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.46.3':
resolution: {integrity: sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==}
'@typescript-eslint/type-utils@8.46.4':
resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/types@8.46.3':
resolution: {integrity: sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==}
'@typescript-eslint/types@8.46.4':
resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.46.3':
resolution: {integrity: sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==}
'@typescript-eslint/typescript-estree@8.46.4':
resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.46.3':
resolution: {integrity: sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==}
'@typescript-eslint/utils@8.46.4':
resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/visitor-keys@8.46.3':
resolution: {integrity: sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==}
'@typescript-eslint/visitor-keys@8.46.4':
resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
@@ -838,8 +838,8 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.13.1:
resolution: {integrity: sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==}
axios@1.13.2:
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
babel-jest@30.2.0:
resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==}
@@ -869,8 +869,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
baseline-browser-mapping@2.8.24:
resolution: {integrity: sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==}
baseline-browser-mapping@2.8.25:
resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==}
hasBin: true
brace-expansion@1.1.12:
@@ -883,8 +883,8 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.27.0:
resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
browserslist@4.28.0:
resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -914,8 +914,8 @@ packages:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
caniuse-lite@1.0.30001753:
resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==}
caniuse-lite@1.0.30001754:
resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
@@ -929,8 +929,8 @@ packages:
resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
engines: {node: '>=8'}
cjs-module-lexer@2.1.0:
resolution: {integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==}
cjs-module-lexer@2.1.1:
resolution: {integrity: sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
@@ -960,8 +960,8 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cron@4.3.3:
resolution: {integrity: sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==}
cron@4.3.4:
resolution: {integrity: sha512-OiO0l73MGhQOZQCjYZ0v7r8yFWpBOWteemwR1RIxiHtfVsIOwiTJZDvg7GmKzggkwC0RO8tI3P1QYBUCIZNYRQ==}
engines: {node: '>=18.x'}
cross-spawn@7.0.6:
@@ -1011,8 +1011,8 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
electron-to-chromium@1.5.244:
resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
electron-to-chromium@1.5.249:
resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==}
emittery@0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@@ -1914,8 +1914,8 @@ packages:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
typescript-eslint@8.46.3:
resolution: {integrity: sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==}
typescript-eslint@8.46.4:
resolution: {integrity: sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -2046,7 +2046,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.28.5
'@babel/helper-validator-option': 7.27.1
browserslist: 4.27.0
browserslist: 4.28.0
lru-cache: 5.1.1
semver: 6.3.1
@@ -2656,14 +2656,14 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
'@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.46.3
'@typescript-eslint/type-utils': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.3
'@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.46.4
'@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.4
eslint: 9.39.1
graphemer: 1.4.0
ignore: 7.0.5
@@ -2673,41 +2673,41 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.46.3(eslint@9.39.1)(typescript@5.9.3)':
'@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.46.3
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.3
'@typescript-eslint/scope-manager': 8.46.4
'@typescript-eslint/types': 8.46.4
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.4
debug: 4.4.3
eslint: 9.39.1
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.46.3(typescript@5.9.3)':
'@typescript-eslint/project-service@8.46.4(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3)
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3)
'@typescript-eslint/types': 8.46.4
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.46.3':
'@typescript-eslint/scope-manager@8.46.4':
dependencies:
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/visitor-keys': 8.46.3
'@typescript-eslint/types': 8.46.4
'@typescript-eslint/visitor-keys': 8.46.4
'@typescript-eslint/tsconfig-utils@8.46.3(typescript@5.9.3)':
'@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.46.3(eslint@9.39.1)(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/types': 8.46.4
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
debug: 4.4.3
eslint: 9.39.1
ts-api-utils: 2.1.0(typescript@5.9.3)
@@ -2715,14 +2715,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.46.3': {}
'@typescript-eslint/types@8.46.4': {}
'@typescript-eslint/typescript-estree@8.46.3(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.46.3(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3)
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/visitor-keys': 8.46.3
'@typescript-eslint/project-service': 8.46.4(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3)
'@typescript-eslint/types': 8.46.4
'@typescript-eslint/visitor-keys': 8.46.4
debug: 4.4.3
fast-glob: 3.3.3
is-glob: 4.0.3
@@ -2733,20 +2733,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.46.3(eslint@9.39.1)(typescript@5.9.3)':
'@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
'@typescript-eslint/scope-manager': 8.46.3
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.46.4
'@typescript-eslint/types': 8.46.4
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
eslint: 9.39.1
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.46.3':
'@typescript-eslint/visitor-keys@8.46.4':
dependencies:
'@typescript-eslint/types': 8.46.3
'@typescript-eslint/types': 8.46.4
eslint-visitor-keys: 4.2.1
'@ungap/structured-clone@1.3.0': {}
@@ -2852,7 +2852,7 @@ snapshots:
asynckit@0.4.0: {}
axios@1.13.1:
axios@1.13.2:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.4
@@ -2914,7 +2914,7 @@ snapshots:
balanced-match@1.0.2: {}
baseline-browser-mapping@2.8.24: {}
baseline-browser-mapping@2.8.25: {}
brace-expansion@1.1.12:
dependencies:
@@ -2929,13 +2929,13 @@ snapshots:
dependencies:
fill-range: 7.1.1
browserslist@4.27.0:
browserslist@4.28.0:
dependencies:
baseline-browser-mapping: 2.8.24
caniuse-lite: 1.0.30001753
electron-to-chromium: 1.5.244
baseline-browser-mapping: 2.8.25
caniuse-lite: 1.0.30001754
electron-to-chromium: 1.5.249
node-releases: 2.0.27
update-browserslist-db: 1.1.4(browserslist@4.27.0)
update-browserslist-db: 1.1.4(browserslist@4.28.0)
bs-logger@0.2.6:
dependencies:
@@ -2958,7 +2958,7 @@ snapshots:
camelcase@6.3.0: {}
caniuse-lite@1.0.30001753: {}
caniuse-lite@1.0.30001754: {}
chalk@4.1.2:
dependencies:
@@ -2969,7 +2969,7 @@ snapshots:
ci-info@4.3.1: {}
cjs-module-lexer@2.1.0: {}
cjs-module-lexer@2.1.1: {}
cliui@8.0.1:
dependencies:
@@ -2995,7 +2995,7 @@ snapshots:
convert-source-map@2.0.0: {}
cron@4.3.3:
cron@4.3.4:
dependencies:
'@types/luxon': 3.7.1
luxon: 3.7.2
@@ -3030,7 +3030,7 @@ snapshots:
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.244: {}
electron-to-chromium@1.5.249: {}
emittery@0.13.1: {}
@@ -3638,7 +3638,7 @@ snapshots:
'@jest/types': 30.2.0
'@types/node': 24.10.0
chalk: 4.1.2
cjs-module-lexer: 2.1.0
cjs-module-lexer: 2.1.1
collect-v8-coverage: 1.0.3
glob: 10.4.5
graceful-fs: 4.2.11
@@ -4080,12 +4080,12 @@ snapshots:
type-fest@4.41.0: {}
typescript-eslint@8.46.3(eslint@9.39.1)(typescript@5.9.3):
typescript-eslint@8.46.4(eslint@9.39.1)(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/parser': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/eslint-plugin': 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
eslint: 9.39.1
typescript: 5.9.3
transitivePeerDependencies:
@@ -4122,9 +4122,9 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
'@unrs/resolver-binding-win32-x64-msvc': 1.11.1
update-browserslist-db@1.1.4(browserslist@4.27.0):
update-browserslist-db@1.1.4(browserslist@4.28.0):
dependencies:
browserslist: 4.27.0
browserslist: 4.28.0
escalade: 3.2.0
picocolors: 1.1.1

View File

@@ -117,6 +117,11 @@ export class QueueCleaner {
return { type: 'seriesIdMismatch', shouldBlock: this.rules.blockRemovedSeriesIdMismatchReleases };
}
if (this.rules.removeEpisodeCountMismatch && message.includes('Episode file on disk contains more episodes than this file contains')) {
this.log('debug', 'Item matched episode count mismatch rule', item.title);
return { type: 'episodeCountMismatch', shouldBlock: this.rules.blockRemovedEpisodeCountMismatchReleases };
}
if (this.rules.removeUndeterminedSample && message.includes('Unable to determine if file is a sample')) {
this.log('debug', 'Item matched undetermined sample rule', item.title);
return { type: 'undeterminedSample', shouldBlock: this.rules.blockRemovedUndeterminedSampleReleases };

View File

@@ -26,6 +26,8 @@ const rulesFromEnv: RuleConfig = {
removeNotAnUpgrade: parseBooleanEnv('REMOVE_NOT_AN_UPGRADE'),
removeSeriesIdMismatch: parseBooleanEnv('REMOVE_SERIES_ID_MISMATCH'),
blockRemovedSeriesIdMismatchReleases: parseBooleanEnv('BLOCK_REMOVED_SERIES_ID_MISMATCH_RELEASES'),
removeEpisodeCountMismatch: parseBooleanEnv('REMOVE_EPISODE_COUNT_MISMATCH'),
blockRemovedEpisodeCountMismatchReleases: parseBooleanEnv('BLOCK_REMOVED_EPISODE_COUNT_MISMATCH_RELEASES'),
removeUndeterminedSample: parseBooleanEnv('REMOVE_UNDETERMINED_SAMPLE'),
// Keep legacy misspelling for backward compatibility with existing deployments.
blockRemovedUndeterminedSampleReleases: getNormalizedEnvBoolean([

View File

@@ -8,6 +8,8 @@ export interface RuleConfig {
removeNotAnUpgrade: boolean;
removeSeriesIdMismatch: boolean;
blockRemovedSeriesIdMismatchReleases: boolean;
removeEpisodeCountMismatch: boolean;
blockRemovedEpisodeCountMismatchReleases: boolean;
removeUndeterminedSample: boolean;
blockRemovedUndeterminedSampleReleases: boolean;
}
@@ -43,7 +45,7 @@ export interface StatusMessage {
messages?: string[];
}
export type RuleType = 'quality' | 'archive' | 'noFiles' | 'notAnUpgrade' | 'seriesIdMismatch' | 'undeterminedSample';
export type RuleType = 'quality' | 'archive' | 'noFiles' | 'notAnUpgrade' | 'seriesIdMismatch' | 'episodeCountMismatch' | 'undeterminedSample';
export interface RuleMatch {
type: RuleType;

View File

@@ -1,6 +1,6 @@
import { QueueCleaner, QueueCleanerOptions } from '../src/cleaner';
import { SonarrClient } from '../src/sonarr';
import { createMockInstance, createRuleConfig, createMockQueueItem, createQualityBlockedItem, createArchiveBlockedItem, createNoFilesBlockedItem, createNotAnUpgradeItem, createSeriesIdMismatchItem, createUndeterminedSampleItem } from './test-utils';
import { createMockInstance, createRuleConfig, createMockQueueItem, createQualityBlockedItem, createArchiveBlockedItem, createNoFilesBlockedItem, createNotAnUpgradeItem, createSeriesIdMismatchItem, createEpisodeCountMismatchItem, createUndeterminedSampleItem } from './test-utils';
jest.mock('../src/sonarr');
const MockedSonarrClient = SonarrClient as jest.MockedClass<typeof SonarrClient>;
@@ -270,6 +270,53 @@ describe('QueueCleaner', () => {
});
});
describe('episode count mismatch items', () => {
it('should remove episode count mismatch items when enabled', async () => {
const cleaner = createCleaner({
rules: createRuleConfig({ removeEpisodeCountMismatch: true })
});
const items = [createEpisodeCountMismatchItem()];
mockSonarrClient.getQueue.mockResolvedValue(items);
await cleaner.cleanQueue();
expect(mockSonarrClient.removeFromQueue).toHaveBeenCalledWith(123);
expect(mockSonarrClient.blockRelease).not.toHaveBeenCalled();
});
it('should block episode count mismatch items when blocking enabled', async () => {
const cleaner = createCleaner({
rules: createRuleConfig({
removeEpisodeCountMismatch: true,
blockRemovedEpisodeCountMismatchReleases: true
})
});
const items = [createEpisodeCountMismatchItem()];
mockSonarrClient.getQueue.mockResolvedValue(items);
await cleaner.cleanQueue();
expect(mockSonarrClient.blockRelease).toHaveBeenCalledWith(123);
expect(mockSonarrClient.removeFromQueue).not.toHaveBeenCalled();
});
it('should skip episode count mismatch items when disabled', async () => {
const cleaner = createCleaner({
rules: createRuleConfig({ removeEpisodeCountMismatch: false })
});
const items = [createEpisodeCountMismatchItem()];
mockSonarrClient.getQueue.mockResolvedValue(items);
await cleaner.cleanQueue();
expect(mockSonarrClient.removeFromQueue).not.toHaveBeenCalled();
expect(mockSonarrClient.blockRelease).not.toHaveBeenCalled();
});
});
it('should process multiple matching items', async () => {
const cleaner = createCleaner({
rules: createRuleConfig({

View File

@@ -14,6 +14,8 @@ export const createRuleConfig = (overrides: Partial<RuleConfig> = {}): RuleConfi
removeNotAnUpgrade: false,
removeSeriesIdMismatch: false,
blockRemovedSeriesIdMismatchReleases: false,
removeEpisodeCountMismatch: false,
blockRemovedEpisodeCountMismatchReleases: false,
removeUndeterminedSample: false,
blockRemovedUndeterminedSampleReleases: false,
...overrides
@@ -75,6 +77,14 @@ export const createSeriesIdMismatchItem = (overrides: Partial<QueueItem> = {}):
...overrides
});
export const createEpisodeCountMismatchItem = (overrides: Partial<QueueItem> = {}): QueueItem =>
createMockQueueItem({
statusMessages: [{
messages: ['Episode file on disk contains more episodes than this file contains']
}],
...overrides
});
export const createUndeterminedSampleItem = (overrides: Partial<QueueItem> = {}): QueueItem =>
createMockQueueItem({
statusMessages: [{