Compare commits

..

110 Commits

Author SHA1 Message Date
dependabot[bot]
5449bc075b Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 20:22:21 +00:00
David
6e3f82d112 Merge pull request #383 from dmunozv04/dependabot/github_actions/actions/download-artifact-6
Bump actions/download-artifact from 5 to 6
2025-12-08 22:37:16 +01:00
dependabot[bot]
fa41363048 Bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 22:37:01 +01:00
David
e0bba121ff Merge pull request #382 from dmunozv04/dependabot/github_actions/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-12-08 22:36:33 +01:00
dependabot[bot]
3bb1ad89a6 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 22:36:23 +01:00
David
b999d34b1e Merge pull request #387 from dmunozv04/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-12-08 22:35:33 +01:00
dependabot[bot]
e9519d0e05 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 22:35:06 +01:00
dmunozv04
1116a1f1d5 Fix markdownlint 2025-12-01 20:45:50 +01:00
dmunozv04
a92c0255dc Add warning about shorts not working
Fixes #370
2025-12-01 20:45:05 +01:00
dmunozv04
12bed77cca bump version 2025-10-19 19:42:00 +02:00
David
dab84dec96 Merge pull request #374 from dmunozv04/dependabot/github_actions/peter-evans/dockerhub-description-5
Bump peter-evans/dockerhub-description from 4 to 5
2025-10-13 09:26:40 +02:00
dependabot[bot]
aaf1f64ec7 Bump peter-evans/dockerhub-description from 4 to 5
Bumps [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) from 4 to 5.
- [Release notes](https://github.com/peter-evans/dockerhub-description/releases)
- [Commits](https://github.com/peter-evans/dockerhub-description/compare/v4...v5)

---
updated-dependencies:
- dependency-name: peter-evans/dockerhub-description
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 20:15:45 +00:00
David
49ea01dd9c Merge pull request #362 from dmunozv04:clarify-muting-not-working-airplay
Clarify ad muting not working when using airplay
2025-09-24 14:40:39 +02:00
dmunozv04
2a2949f552 Clarify ad muting not working when using airplay 2025-09-24 14:38:42 +02:00
David
85b4124a52 Merge pull request #359 from Wobak/patch-1
Update docker-compose.yml
2025-09-23 11:48:03 +02:00
Wobak
da7dcf67fe Update docker-compose.yml
removed version as not recommended anymore and changed indentation from tabs to spaces
2025-09-22 20:10:17 +02:00
David
b4d1feb3a9 Merge pull request #358 from dmunozv04/semver-docker-tags
Add semver tags to docker build
2025-09-21 11:23:20 +02:00
dmunozv04
6afd1bcbaa Add semver tags to docker build 2025-09-18 10:40:52 +02:00
David
516326e0ff Merge pull request #351 from dmunozv04/dependabot/pip/ssdp-1.3.1
Bump ssdp from 1.3.0 to 1.3.1
2025-09-11 13:44:31 +02:00
dependabot[bot]
461b8bfde7 Bump ssdp from 1.3.0 to 1.3.1
Bumps [ssdp](https://github.com/codingjoe/ssdp) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/codingjoe/ssdp/releases)
- [Commits](https://github.com/codingjoe/ssdp/compare/1.3.0...1.3.1)

---
updated-dependencies:
- dependency-name: ssdp
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 11:44:03 +00:00
David
580ac5e3e1 Merge pull request #350 from dmunozv04/dependabot/pip/xmltodict-0.15.1
Bump xmltodict from 0.14.2 to 0.15.1
2025-09-11 13:42:55 +02:00
dependabot[bot]
52a221c4e0 Bump xmltodict from 0.14.2 to 0.15.1
Bumps [xmltodict](https://github.com/martinblech/xmltodict) from 0.14.2 to 0.15.1.
- [Changelog](https://github.com/martinblech/xmltodict/blob/master/CHANGELOG.md)
- [Commits](https://github.com/martinblech/xmltodict/compare/v0.14.2...v0.15.1)

---
updated-dependencies:
- dependency-name: xmltodict
  dependency-version: 0.15.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 13:42:44 +02:00
David
e6dff63b19 Merge pull request #340 from dmunozv04/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-09-11 13:41:39 +02:00
pre-commit-ci[bot]
8bab77237d [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/astral-sh/ruff-pre-commit: v0.12.7 → v0.12.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.7...v0.12.12)
2025-09-11 13:41:30 +02:00
David
31a6d260e5 Merge pull request #343 from dmunozv04/dependabot/github_actions/actions/download-artifact-5
Bump actions/download-artifact from 4 to 5
2025-09-11 13:40:49 +02:00
dependabot[bot]
34256b5c5e Bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 13:40:41 +02:00
David
91842c18f2 Merge pull request #353 from dmunozv04/dependabot/github_actions/actions/attest-build-provenance-3
Bump actions/attest-build-provenance from 2 to 3
2025-09-11 13:40:21 +02:00
dependabot[bot]
accb685bf3 Bump actions/attest-build-provenance from 2 to 3
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2 to 3.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 13:40:09 +02:00
David
f68311cbf6 Merge pull request #354 from dmunozv04/dependabot/github_actions/actions/setup-python-6
Bump actions/setup-python from 5 to 6
2025-09-11 13:39:43 +02:00
dependabot[bot]
aa76d130d8 Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 20:30:21 +00:00
David
258239338e Merge pull request #342 from dmunozv04/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-31 12:26:37 -04:00
dependabot[bot]
21f52537d8 Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-31 16:22:01 +00:00
David
f6bfd9af98 Merge pull request #341 from dmunozv04/dependabot/pip/aiohttp-3.12.15
Bump aiohttp from 3.12.14 to 3.12.15
2025-08-31 12:19:59 -04:00
dependabot[bot]
4a57cce9bb Bump aiohttp from 3.12.14 to 3.12.15
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.12.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-30 17:59:01 +00:00
David
6524361d5d Merge pull request #348 from dmunozv04/update-ci-2
Only login to dockerhub if username is provided via secret
2025-08-30 13:57:47 -04:00
dmunozv04
27ecc54d93 Only login to dockerhub if username is provided via secret 2025-08-30 13:57:38 -04:00
David
3b7617ef14 Merge pull request #345 from gaiar/add-legacy-architecture-support
Add support for legacy architectures (i386 and armv6)
2025-08-30 13:48:44 -04:00
Gaiar Baimuratov
aa35610c67 Add support for legacy architectures (i386 and armv6)
Extends Docker build to include:
- linux/386: 32-bit x86 systems (legacy servers, older PCs)
- linux/arm/v6: ARMv6 devices (Raspberry Pi Zero, Pi 1, older ARM systems)

Motivation: Many users have legacy but capable hardware that can effectively
run containerized applications. These older systems often have sufficient
resources for iSponsorBlockTV but lack support in most modern Docker images.

Technical impact:
- Zero breaking changes for existing users
- Leverages existing QEMU cross-compilation infrastructure
- Minimal build time increase (~10-15%)
- Broader device compatibility without maintenance overhead
2025-08-30 13:48:06 -04:00
David
d581f7ee07 Merge pull request #347 from dmunozv04/update-ci
Update ci
2025-08-30 13:47:50 -04:00
dmunozv04
8e01755550 Only build on main/release/tag 2025-08-30 19:39:28 +02:00
dmunozv04
56b42e26ff Update release
- update pyapp
- ignore attestation errors
2025-08-21 13:57:24 +02:00
dmunozv04
c88861822c Fix markdownlint 2025-08-10 19:33:41 +02:00
dmunozv04
315ac2c726 Update README.md 2025-08-10 19:28:12 +02:00
dmunozv04
390eb68310 Bump version 2025-08-10 19:05:59 +02:00
pre-commit-ci[bot]
7652c9f260 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.9 → v0.12.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.9...v0.12.7)
- [github.com/igorshubovych/markdownlint-cli: v0.44.0 → v0.45.0](https://github.com/igorshubovych/markdownlint-cli/compare/v0.44.0...v0.45.0)
2025-08-10 18:51:03 +02:00
dmunozv04
acf074e860 Various fixes to work with newer textual versions 2025-08-10 18:50:32 +02:00
dependabot[bot]
123f3d4000 Bump textual from 2.1.2 to 5.0.1
Bumps [textual](https://github.com/Textualize/textual) from 2.1.2 to 5.0.1.
- [Release notes](https://github.com/Textualize/textual/releases)
- [Changelog](https://github.com/Textualize/textual/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Textualize/textual/compare/v2.1.2...v5.0.1)

---
updated-dependencies:
- dependency-name: textual
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 18:50:32 +02:00
dependabot[bot]
303d805e5d Bump rich from 14.0.0 to 14.1.0
Bumps [rich](https://github.com/Textualize/rich) from 14.0.0 to 14.1.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.0.0...v14.1.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 17:54:50 +02:00
dependabot[bot]
c779e83d96 Bump rich-click from 1.8.8 to 1.8.9
Bumps [rich-click](https://github.com/ewels/rich-click) from 1.8.8 to 1.8.9.
- [Release notes](https://github.com/ewels/rich-click/releases)
- [Changelog](https://github.com/ewels/rich-click/blob/v1.8.9/CHANGELOG.md)
- [Commits](https://github.com/ewels/rich-click/compare/v1.8.8...v1.8.9)

---
updated-dependencies:
- dependency-name: rich-click
  dependency-version: 1.8.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 17:52:30 +02:00
dependabot[bot]
aac4b333c2 Bump aiohttp from 3.11.18 to 3.12.14
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.11.18 to 3.12.14.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.11.18...v3.12.14)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 17:51:24 +02:00
David
fa83124002 Merge pull request #334 from desofity/trust-system-proxy-by-default
Added proxy support
2025-07-28 22:19:04 +02:00
pre-commit-ci[bot]
76b82e8848 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-07-28 17:23:53 +00:00
desofity
26fec272a3 fixed prompts 2025-07-28 20:22:50 +03:00
desofity
3fdcee71fd Merge branch 'trust-system-proxy-by-default' of https://github.com/desofity/iSponsorBlockTV into trust-system-proxy-by-default 2025-07-18 14:25:58 +03:00
desofity
1a58ce6a57 split label text to pass deepSource python check 2025-07-18 14:23:08 +03:00
pre-commit-ci[bot]
90d313049b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-07-18 11:19:13 +00:00
desofity
cd7e5c83c7 added param to Config constructor 2025-07-18 14:12:15 +03:00
desofity
bf1fe68089 messing with gui 3 2025-07-18 00:06:21 +03:00
desofity
d179fe2b79 messing with gui 2 2025-07-17 23:52:25 +03:00
desofity
4724ee1a39 messing with gui 2025-07-17 23:40:25 +03:00
desofity
bb7fdbfb06 option added to GUI setup 2025-07-17 23:19:47 +03:00
desofity
930db16f53 added prompt for use_proxy in config_setup 2025-07-17 22:05:03 +03:00
desofity
8ed1cb4b00 added use_proxy setting in configuration file 2025-07-17 22:00:57 +03:00
desofity
65ecbb9193 trying to just fix aiohttp.ClientSession call 2025-07-17 21:17:46 +03:00
dmunozv04
f2155abad3 Bump version 2025-05-30 21:56:45 +02:00
David
edbea793ed Merge pull request #312 from dmunozv04/fix-311
Fixes constant "new decive connected"
2025-05-30 21:55:46 +02:00
dmunozv04
df629805c2 Fixes constant "new decive connected" 2025-05-30 21:55:00 +02:00
dmunozv04
ad9834b9f0 Bump version 2025-05-30 09:56:24 +02:00
David
97e7b31d9c Merge pull request #310 from dmunozv04/fix-error-401-connect
Fix error 401 connect
2025-05-28 23:52:45 +02:00
David
b5d275e01e Merge branch 'main' into fix-error-401-connect 2025-05-28 23:48:35 +02:00
pre-commit-ci[bot]
98c1211b09 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-28 21:48:24 +00:00
David
57f33ec354 Merge pull request #309 from dmunozv04/http-tracing
Add http tracing
2025-05-28 23:47:04 +02:00
pre-commit-ci[bot]
9f6a18a006 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-28 21:43:15 +00:00
dmunozv04
fd6f0d7283 Attempt to fix the issue 2025-05-28 00:18:32 +02:00
dmunozv04
166e238f41 Mimick YouTube iOS app 2025-05-25 14:02:59 +02:00
dmunozv04
8ecaa7e86f Add http tracing 2025-05-22 00:33:36 +02:00
dmunozv04
cafdf4f962 Bump version 2025-05-19 10:24:05 +02:00
David
3a80c76fb6 Merge pull request #292 from dmunozv04/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-05-19 10:16:30 +02:00
pre-commit-ci[bot]
a4f6462026 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.9)
2025-05-19 10:16:12 +02:00
David
c4571ad90b Merge pull request #296 from dmunozv04/dependabot/pip/aiohttp-3.11.18
Bump aiohttp from 3.11.16 to 3.11.18
2025-05-19 10:15:36 +02:00
dependabot[bot]
c51c47b566 Bump aiohttp from 3.11.16 to 3.11.18
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.11.16 to 3.11.18.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.11.16...v3.11.18)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.11.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 08:15:16 +00:00
David
72ada4558e Merge pull request #300 from dmunozv04/improve-watchdog
Improve watchdog/ Fix ad muting/skipping
2025-05-14 23:56:03 +02:00
David
6219cfe0d0 Merge branch 'main' into improve-watchdog 2025-05-14 23:54:57 +02:00
pre-commit-ci[bot]
b2dfe35698 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-14 21:54:36 +00:00
David
af35982ac0 Merge pull request #278 from sternma/minimum-skip-length
Add support for minimum skip length
2025-05-14 23:52:17 +02:00
Matthew Stern
fb30d7e4cb Merge branch 'dmunozv04:main' into minimum-skip-length 2025-05-14 00:16:42 -04:00
pre-commit-ci[bot]
f04f7560b2 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-05-14 04:16:24 +00:00
Matthew Stern
d55fceda18 Merge pull request #1 from SimoMay/minimum-skip-length
Fix feedback for #278 (duplicate code)
2025-05-14 00:16:16 -04:00
dmunozv04
a6dacc1d84 Fix adPlaying logic 2025-05-11 01:37:28 +02:00
dmunozv04
a67e3eb860 Compare strings 2025-05-10 22:28:01 +02:00
dmunozv04
a9d64af2ac Fix muting not working 2025-05-10 18:27:18 +02:00
dmunozv04
fc8d1770cd Improve watchdog 2025-05-03 21:42:13 +02:00
Mohamed
82ce3e60e9 Remove redundant code for minimum skip length configuration 2025-04-29 17:52:11 +02:00
Mohamed
4417592b6b Fix: Correct line length error in setup wizard label
Split long string literal in the Minimum Skip Length label to adhere
to maximum line length constraints (FLK-E501)
2025-04-29 17:39:52 +02:00
Matthew Stern
2dbeed99bc Merge branch 'dmunozv04:main' into minimum-skip-length 2025-03-27 09:53:06 -04:00
pre-commit-ci[bot]
2124fff81b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-03-14 23:30:01 +00:00
Matthew Stern
aabf5aa2bc Merge remote-tracking branch 'origin/minimum-skip-length' into minimum-skip-length 2025-03-14 19:29:49 -04:00
Matthew Stern
068623bb03 Add input validation for min skip length 2025-03-14 19:28:54 -04:00
pre-commit-ci[bot]
b93f480848 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-03-14 23:11:31 +00:00
Matthew Stern
e9fdc49480 Add minimum skip length input 2025-03-14 19:06:15 -04:00
Matthew Stern
4a55fe9539 set minimum skip length to 1 2025-03-14 19:05:55 -04:00
Matthew Stern
328e70a175 add minimum skip length manager 2025-03-14 19:00:05 -04:00
Matthew Stern
7a1d8967ae set default minimum skip length to 1 2025-03-14 18:42:47 -04:00
pre-commit-ci[bot]
33b0b6d224 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-03-14 18:39:52 -04:00
bourkemcrobbo
e0c4322524 Updated setup to match new format. Set default value of skip length to 0 so user has to explicitly enable functionality 2025-03-14 18:39:52 -04:00
pre-commit-ci[bot]
c360e2582e [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-03-14 18:39:16 -04:00
bourkemcrobbo
7b3e618628 Fixed bad static method argument 2025-03-14 18:39:16 -04:00
pre-commit-ci[bot]
886997beab [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2025-03-14 18:39:16 -04:00
bourkemcrobbo
ee786a53b9 Fixed bad static method argument 2025-03-14 18:35:54 -04:00
bourkemcrobbo
0b785da448 Added support for specifying minimum skip length 2025-03-14 18:35:54 -04:00
17 changed files with 381 additions and 146 deletions

View File

@@ -6,7 +6,8 @@ on:
branches:
- '*'
tags:
- 'v*'
- 'v*.*.*'
- 'v*.*.*-*'
pull_request:
branches:
- '*'
@@ -27,7 +28,7 @@ jobs:
steps:
# Get the repository's code
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
# Generate docker tags
- name: Docker meta
@@ -40,7 +41,9 @@ jobs:
tags: |
type=raw,value=develop,priority=900,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=pr
type=ref,event=tag
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}
type=semver,pattern=v{{major}}.{{minor}}
type=ref,event=branch
type=schedule
@@ -53,7 +56,7 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && env.DOCKERHUB_USERNAME != ''
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -71,7 +74,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64, linux/arm64, linux/arm/v7
platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/386, linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -11,12 +11,9 @@ name: Release Package
on:
push:
branches:
- '*'
- 'main'
tags:
- 'v*'
pull_request:
branches:
- '*'
release:
types: [published]
@@ -33,10 +30,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -49,7 +46,7 @@ jobs:
run: python -m hatch build
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: sdist-and-wheel
path: dist/*
@@ -103,17 +100,17 @@ jobs:
CARGO_BUILD_TARGET: ${{ matrix.job.target }}
PYAPP_DISTRIBUTION_VARIANT_CPU: ${{ matrix.job.cpu_variant }}
PYAPP_REPO: pyapp # Use local copy of pyapp (needed for cross-compiling)
PYAPP_VERSION: v0.27.0
PYAPP_VERSION: v0.28.0
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Clone PyApp
run: git clone --depth 1 --branch $PYAPP_VERSION https://github.com/ofek/pyapp $PYAPP_REPO
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -144,7 +141,7 @@ jobs:
hatch --version
- name: Get artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: sdist-and-wheel
path: ${{ github.workspace }}/dist
@@ -162,12 +159,13 @@ jobs:
mv dist/binary/iSponsorBlockTV* dist/binary/iSponsorBlockTV-${{ matrix.job.release_suffix }}
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-path: dist/binary/*
continue-on-error: true # Continue if attestation fails (it will fail on forks)
- name: Upload built binary package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: binaries-${{ matrix.job.release_suffix }}
path: dist/binary/*
@@ -183,7 +181,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Get artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: sdist-and-wheel
path: dist
@@ -202,7 +200,7 @@ jobs:
if: github.event_name == 'release' && github.event.action == 'published'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
name: Get artifact
with:
path: dist

View File

@@ -18,11 +18,11 @@ jobs:
steps:
# Get the repository's code
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
# Update description
- name: Update repo description
uses: peter-evans/dockerhub-description@v4
uses: peter-evans/dockerhub-description@v5
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -3,7 +3,7 @@
# Inspired by textual pre-commit config
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-ast # simply checks whether the files parse as valid python
- id: check-builtin-literals # requires literal syntax when initializing empty or zero python builtin types
@@ -19,13 +19,13 @@ repos:
- id: mixed-line-ending # replaces or checks mixed line ending
- id: trailing-whitespace # checks for trailing whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.4
rev: v0.12.12
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]
- id: ruff-format
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.44.0
rev: v0.45.0
hooks:
- id: markdownlint
args: ["--fix"]

View File

@@ -5,10 +5,11 @@
[![GitHub Release](https://img.shields.io/github/v/release/dmunozv04/isponsorblocktv?logo=GitHub&style=flat)](https://github.com/dmunozv04/iSponsorBlockTV/releases/latest)
[![GitHub Repo stars](https://img.shields.io/github/stars/dmunozv04/isponsorblocktv?style=flat)](https://github.com/dmunozv04/isponsorblocktv)
Skip sponsor segments in YouTube videos playing on a YouTube TV device (see
below for compatibility details).
This project is written in asynchronous python and should be pretty quick.
iSponsorBlockTV is a self-hosted application that connects to your YouTube TV
app (see compatibility below) and automatically skips segments (like Sponsors
or intros) in YouTube videos using the [SponsorBlock](https://sponsor.ajay.app/)
API. It can also auto mute and press the "Skip Ad" button the moment it becomes
available on YouTube ads.
## Installation
@@ -22,7 +23,7 @@ Open an issue/pull request if you have tested a device that isn't listed here.
| Device | Status |
|:-------------------|:------:|
| Apple TV | ✅ |
| Apple TV | ✅* |
| Samsung TV (Tizen) | ✅ |
| LG TV (WebOS) | ✅ |
| Android TV | ✅ |
@@ -35,17 +36,22 @@ Open an issue/pull request if you have tested a device that isn't listed here.
| Xbox One/Series | ✅ |
| Playstation 4/5 | ✅ |
*Ad muting won't work when using AirPlay to send the audio to another speaker.
** Shorts aren't fully supported due to limitations on YouTube's side.
A single short can be seen by either selecting the "Disconnect" option in the
warning shown
or by long pressing the thumbnail to open the menu and clicking play from there
## Usage
Run iSponsorBlockTV on a computer that has network access.
Run iSponsorBlockTV on a computer that has network access. It doesn't need to
be on the same network as the device, only access to youtube.com is required.
Auto discovery will require the computer to be on the same network as the device
during setup.
The device can also be manually added to iSponsorBlockTV with a YouTube TV code.
This code can be found in the settings page of your YouTube application.
It connects to the device, watches its activity and skips any sponsor segment
using the [SponsorBlock](https://sponsor.ajay.app/) API.
It can also skip/mute YouTube ads.
This code can be found in the settings page of your YouTube TV application.
## Libraries used
@@ -71,11 +77,9 @@ It can also skip/mute YouTube ads.
## Contributors
- [dmunozv04](https://github.com/dmunozv04) - creator and maintainer
- [HaltCatchFire](https://github.com/HaltCatchFire) - updated dependencies and
improved skip logic
- [Oxixes](https://github.com/oxixes) - added support for channel whitelist and
minor improvements
[![Contributors](https://contrib.rocks/image?repo=dmunozv04/iSponsorBlockTV)](https://github.com/dmunozv04/iSponsorBlockTV/graphs/contributors)
Made with [contrib.rocks](https://contrib.rocks).
## License

View File

@@ -12,6 +12,7 @@
"skip_count_tracking": true,
"mute_ads": true,
"skip_ads": true,
"minimum_skip_length": 1,
"auto_play": true,
"join_name": "iSponsorBlockTV",
"apikey": "",
@@ -19,5 +20,6 @@
{"id": "",
"name": ""
}
]
],
"use_proxy": false
}

View File

@@ -1,8 +1,7 @@
version: '3.3'
services:
iSponsorBlockTV:
image: ghcr.io/dmunozv04/isponsorblocktv
container_name: iSponsorBlockTV
restart: unless-stopped
volumes:
- /PATH_TO_YOUR_DATA_DIR:/app/data
iSponsorBlockTV:
image: ghcr.io/dmunozv04/isponsorblocktv
container_name: iSponsorBlockTV
restart: unless-stopped
volumes:
- /PATH_TO_YOUR_DATA_DIR:/app/data

View File

@@ -1,6 +1,6 @@
[project]
name = "iSponsorBlockTV"
version = "2.4.0"
version = "2.6.1"
authors = [
{"name" = "dmunozv04"}
]

View File

@@ -1,10 +1,10 @@
aiohttp==3.11.16
aiohttp==3.12.15
appdirs==1.4.4
async-cache==1.1.1
pyytlounge==2.3.0
rich==14.0.0
ssdp==1.3.0
textual==2.1.2
rich==14.1.0
ssdp==1.3.1
textual==5.3.0
textual-slider==0.2.0
xmltodict==0.14.2
rich_click==1.8.8
xmltodict==0.15.1
rich_click==1.8.9

View File

@@ -27,6 +27,7 @@ class ApiHelper:
self.skip_count_tracking = config.skip_count_tracking
self.web_session = web_session
self.num_devices = len(config.devices)
self.minimum_skip_length = config.minimum_skip_length
# Not used anymore, maybe it can stay here a little longer
@AsyncLRU(maxsize=10)
@@ -139,10 +140,10 @@ class ApiHelper:
if str(i["videoID"]) == str(vid_id):
response_json = i
break
return self.process_segments(response_json)
return self.process_segments(response_json, self.minimum_skip_length)
@staticmethod
def process_segments(response):
def process_segments(response, minimum_skip_length):
segments = []
ignore_ttl = True
try:
@@ -184,7 +185,9 @@ class ApiHelper:
segment_dict["start"] = segment_before_start
segment_dict["UUID"].extend(segment_before_UUID)
segments.pop()
segments.append(segment_dict)
# Only add segments greater than minimum skip length
if segment_dict["end"] - segment_dict["start"] > minimum_skip_length:
segments.append(segment_dict)
except BaseException:
pass
return segments, ignore_ttl

View File

@@ -5,6 +5,7 @@ import aiohttp
from . import api_helpers, ytlounge
# Constants for user input prompts
USE_PROXY_PROMPT = "Do you want to use system-wide proxy? (y/N)"
ATVS_REMOVAL_PROMPT = (
"Do you want to remove the legacy 'atvs' entry (the app won't start with it present)? (y/N) "
)
@@ -24,6 +25,10 @@ SEARCH_CHANNEL_PROMPT = 'Enter a channel name or "/exit" to exit: '
SELECT_CHANNEL_PROMPT = "Select one option of the above [0-6]: "
ENTER_CHANNEL_ID_PROMPT = "Enter a channel ID: "
ENTER_CUSTOM_CHANNEL_NAME_PROMPT = "Enter the channel name: "
MINIMUM_SKIP_PROMPT = "Do you want to specify a minimum length of segment to skip? (y/N)"
MINIMUM_SKIP_SPECIFICATION_PROMPT = (
"Enter minimum length of segment to skip in seconds (enter 0 to disable):"
)
REPORT_SKIPPED_SEGMENTS_PROMPT = (
"Do you want to report skipped segments to sponsorblock. Only the segment"
" UUID will be sent? (Y/n) "
@@ -41,8 +46,8 @@ def get_yn_input(prompt):
return None
async def create_web_session():
return aiohttp.ClientSession()
async def create_web_session(use_proxy):
return aiohttp.ClientSession(trust_env=use_proxy)
async def pair_device(web_session: aiohttp.ClientSession):
@@ -71,8 +76,12 @@ async def pair_device(web_session: aiohttp.ClientSession):
def main(config, debug: bool) -> None:
print("Welcome to the iSponsorBlockTV cli setup wizard")
choice = get_yn_input(USE_PROXY_PROMPT)
config.use_proxy = choice == "y"
loop = asyncio.get_event_loop_policy().get_event_loop()
web_session = loop.run_until_complete(create_web_session())
web_session = loop.run_until_complete(create_web_session(config.use_proxy))
if debug:
loop.set_debug(True)
asyncio.set_event_loop(loop)
@@ -171,6 +180,21 @@ def main(config, debug: bool) -> None:
config.channel_whitelist = channel_whitelist
# Ask for minimum skip length. Confirm input is an integer
minimum_skip_length = config.minimum_skip_length
choice = get_yn_input(MINIMUM_SKIP_PROMPT)
if choice == "y":
while True:
try:
minimum_skip_length = int(input(MINIMUM_SKIP_SPECIFICATION_PROMPT))
break
except ValueError:
print("You entered a non integer value, try again.")
continue
config.minimum_skip_length = minimum_skip_length
choice = get_yn_input(REPORT_SKIPPED_SEGMENTS_PROMPT)
config.skip_count_tracking = choice != "n"

View File

@@ -0,0 +1,25 @@
class AiohttpTracer:
def __init__(self, logger):
self.logger = logger
async def on_request_start(self, session, context, params):
self.logger.debug(f"Request started ({id(context):#x}): {params.method} {params.url}")
async def on_request_end(self, session, context, params):
self.logger.debug(f"Request ended ({id(context):#x}): {params.response.status}")
async def on_request_exception(self, session, context, params):
self.logger.debug(f"Request exception ({id(context):#x}): {params.exception}")
async def on_response_chunk_received(self, session, context, params):
chunk_size = len(params.chunk)
try:
# Try to decode as text
text = params.chunk.decode("utf-8")
self.logger.debug(f"Response chunk ({id(context):#x}) {chunk_size} bytes: {text}")
except UnicodeDecodeError:
# If not valid UTF-8, show as hex
hex_data = params.chunk.hex()
self.logger.debug(
f"Response chunk ({id(context):#x}) ({chunk_size} bytes) [HEX]: {hex_data}"
)

View File

@@ -41,8 +41,10 @@ class Config:
self.skip_count_tracking = True
self.mute_ads = False
self.skip_ads = False
self.minimum_skip_length = 1
self.auto_play = True
self.join_name = "iSponsorBlockTV"
self.use_proxy = False
self.__load()
def validate(self):
@@ -130,6 +132,7 @@ class Config:
help="data directory",
)
@click.option("--debug", is_flag=True, help="debug mode")
@click.option("--http-tracing", is_flag=True, help="Enable HTTP request/response tracing")
# legacy commands as arguments
@click.option("--setup", is_flag=True, help="Setup the program graphically", hidden=True)
@click.option(
@@ -139,11 +142,12 @@ class Config:
hidden=True,
)
@click.pass_context
def cli(ctx, data, debug, setup, setup_cli):
def cli(ctx, data, debug, http_tracing, setup, setup_cli):
"""iSponsorblockTV"""
ctx.ensure_object(dict)
ctx.obj["data_dir"] = data
ctx.obj["debug"] = debug
ctx.obj["http_tracing"] = http_tracing
logger = logging.getLogger()
ctx.obj["logger"] = logger
@@ -188,7 +192,7 @@ def start(ctx):
"""Start the main program"""
config = Config(ctx.obj["data_dir"])
config.validate()
main.main(config, ctx.obj["debug"])
main.main(config, ctx.obj["debug"], ctx.obj["http_tracing"])
# Create fake "self" group to show pyapp options in help menu

View File

@@ -7,6 +7,7 @@ from typing import Optional
import aiohttp
from . import api_helpers, ytlounge
from .debug_helpers import AiohttpTracer
class DeviceListener:
@@ -153,14 +154,30 @@ def handle_signal(signum, frame):
raise KeyboardInterrupt()
async def main_async(config, debug):
async def main_async(config, debug, http_tracing):
loop = asyncio.get_event_loop_policy().get_event_loop()
tasks = [] # Save the tasks so the interpreter doesn't garbage collect them
devices = [] # Save the devices to close them later
if debug:
loop.set_debug(True)
tcp_connector = aiohttp.TCPConnector(ttl_dns_cache=300)
web_session = aiohttp.ClientSession(connector=tcp_connector)
# Configure session with tracing if enabled
if http_tracing:
root_logger = logging.getLogger("aiohttp_trace")
tracer = AiohttpTracer(root_logger)
trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(tracer.on_request_start)
trace_config.on_response_chunk_received.append(tracer.on_response_chunk_received)
trace_config.on_request_end.append(tracer.on_request_end)
trace_config.on_request_exception.append(tracer.on_request_exception)
web_session = aiohttp.ClientSession(
trust_env=config.use_proxy, connector=tcp_connector, trace_configs=[trace_config]
)
else:
web_session = aiohttp.ClientSession(trust_env=config.use_proxy, connector=tcp_connector)
api_helper = api_helpers.ApiHelper(config, web_session)
for i in config.devices:
device = DeviceListener(api_helper, config, i, debug, web_session)
@@ -184,5 +201,5 @@ async def main_async(config, debug):
print("Exited")
def main(config, debug):
asyncio.run(main_async(config, debug))
def main(config, debug, http_tracing):
asyncio.run(main_async(config, debug, http_tracing))

View File

@@ -383,3 +383,9 @@ MigrationScreen {
padding: 1;
height: auto;
}
/* Use Proxy */
#useproxy-container{
padding: 1;
height: auto;
}

View File

@@ -13,6 +13,7 @@ from textual.containers import (
ScrollableContainer,
Vertical,
)
from textual.css.query import NoMatches
from textual.events import Click
from textual.screen import Screen
from textual.validation import Function
@@ -233,7 +234,7 @@ class AddDevice(ModalWithClickExit):
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
self.web_session = aiohttp.ClientSession()
self.web_session = aiohttp.ClientSession(trust_env=config.use_proxy)
self.api_helper = api_helpers.ApiHelper(config, self.web_session)
self.devices_discovered_dial = []
@@ -301,7 +302,11 @@ class AddDevice(ModalWithClickExit):
async def task_discover_devices(self):
devices_found = await self.api_helper.discover_youtube_devices_dial()
list_widget: SelectionList = self.query_one("#dial-devices-list")
try:
list_widget: SelectionList = self.query_one("#dial-devices-list")
except NoMatches:
# The widget was not found, probably the screen was dismissed
return
list_widget.clear_options()
if devices_found:
# print(devices_found)
@@ -336,7 +341,7 @@ class AddDevice(ModalWithClickExit):
pairing_code = int(
pairing_code.replace("-", "").replace(" ", "")
) # remove dashes and spaces
device_name = self.parent.query_one("#device-name-input").value
device_name = self.query_one("#device-name-input").value
paired = False
try:
paired = await lounge_controller.pair(pairing_code)
@@ -382,7 +387,7 @@ class AddChannel(ModalWithClickExit):
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
web_session = aiohttp.ClientSession()
web_session = aiohttp.ClientSession(trust_env=config.use_proxy)
self.api_helper = api_helpers.ApiHelper(config, web_session)
def compose(self) -> ComposeResult:
@@ -659,7 +664,7 @@ class ApiKeyManager(Vertical):
@on(Button.Pressed, "#api-key-view")
def pressed_api_key_view(self, event: Button.Pressed):
if "Show" in event.button.label:
if "Show" in str(event.button.label):
event.button.label = "Hide key"
self.query_one("#api-key-input").password = False
else:
@@ -692,6 +697,43 @@ class SkipCategoriesManager(Vertical):
self.config.skip_categories = event.selection_list.selected
class MinimumSkipLengthManager(Vertical):
"""Manager for minimum skip length setting."""
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
def compose(self) -> ComposeResult:
yield Label("Minimum Skip Length", classes="title")
yield Label(
(
"Specify the minimum length a segment must meet in order to skip "
"it (in seconds). Default is 1 second; entering 0 will skip all "
"segments."
),
classes="subtitle",
)
yield Input(
placeholder="Minimum skip length (0 to skip all)",
id="minimum-skip-length-input",
value=str(getattr(self.config, "minimum_skip_length", 1)),
validators=[
Function(
lambda user_input: user_input.isdigit(),
"Please enter a valid non-negative number",
)
],
)
@on(Input.Changed, "#minimum-skip-length-input")
def changed_minimum_skip_length(self, event: Input.Changed):
try:
self.config.minimum_skip_length = int(event.input.value)
except ValueError:
self.config.minimum_skip_length = 1
class SkipCountTrackingManager(Vertical):
"""Manager for skip count tracking, allows to enable/disable skip count tracking."""
@@ -783,10 +825,7 @@ class ChannelWhitelistManager(Vertical):
id="channel-whitelist-subtitle",
)
yield Label(
(
":warning: [#FF0000]You need to set your YouTube Api Key in order to"
" use this feature"
),
("⚠️ [#FF0000]You need to set your YouTube Api Key in order to use this feature"),
id="warning-no-key",
)
with Horizontal(id="add-channel-button-container"):
@@ -853,11 +892,45 @@ class AutoPlayManager(Vertical):
self.config.auto_play = event.checkbox.value
class ISponsorBlockTVSetupMainScreen(Screen):
class UseProxyManager(Vertical):
"""Manager for proxy use, allows enabling/disabling use of proxy."""
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
def compose(self) -> ComposeResult:
yield Label("Use proxy", classes="title")
yield Label(
"This feature allows application to use system proxy,"
" if it is set in environment variables."
" This parameter will be passed in all [i]aiohttp.ClientSession[/i]"
' calls. For further information, see "[i]trust_env[/i]" section at'
" [link='https://docs.aiohttp.org/en/stable/client_reference.html']"
"aiohttp documentation[/link].",
classes="subtitle",
id="useproxy-subtitle",
)
with Horizontal(id="useproxy-container"):
yield Checkbox(
value=self.config.use_proxy,
id="useproxy-switch",
label="Use proxy",
)
@on(Checkbox.Changed, "#useproxy-switch")
def changed_skip(self, event: Checkbox.Changed):
self.config.use_proxy = event.checkbox.value
class ISponsorBlockTVSetup(App):
TITLE = "iSponsorBlockTV"
SUB_TITLE = "Setup Wizard"
BINDINGS = [("q,ctrl+c", "exit_modal", "Exit"), ("s", "save", "Save")]
AUTO_FOCUS = None
CSS_PATH = ( # tcss is the recommended extension for textual css files
"setup-wizard-style.tcss"
)
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
@@ -873,6 +946,11 @@ class ISponsorBlockTVSetupMainScreen(Screen):
yield SkipCategoriesManager(
config=self.config, id="skip-categories-manager", classes="container"
)
yield MinimumSkipLengthManager(
config=self.config,
id="minimum-skip-length-manager",
classes="container",
)
yield SkipCountTrackingManager(
config=self.config, id="count-segments-manager", classes="container"
)
@@ -884,6 +962,7 @@ class ISponsorBlockTVSetupMainScreen(Screen):
)
yield ApiKeyManager(config=self.config, id="api-key-manager", classes="container")
yield AutoPlayManager(config=self.config, id="autoplay-manager", classes="container")
yield UseProxyManager(config=self.config, id="useproxy-manager", classes="container")
def on_mount(self) -> None:
if self.check_for_old_config_entries():
@@ -907,36 +986,13 @@ class ISponsorBlockTVSetupMainScreen(Screen):
@on(Input.Changed, "#api-key-input")
def changed_api_key(self, event: Input.Changed):
try: # ChannelWhitelist might not be mounted
# Show if no api key is set and at least one channel is in the whitelist
self.app.query_one("#warning-no-key").display = (
not event.input.value
) and self.config.channel_whitelist
except BaseException:
self.app.query_one("#warning-no-key").display = bool(
(not event.input.value) and self.config.channel_whitelist
)
except NoMatches:
pass
class ISponsorBlockTVSetup(App):
CSS_PATH = ( # tcss is the recommended extension for textual css files
"setup-wizard-style.tcss"
)
# Bindings for the whole app here, so they are available in all screens
BINDINGS = [("q,ctrl+c", "exit_modal", "Exit"), ("s", "save", "Save")]
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
self.main_screen = ISponsorBlockTVSetupMainScreen(config=self.config)
def on_mount(self) -> None:
self.push_screen(self.main_screen)
def action_save(self) -> None:
self.main_screen.action_save()
def action_exit_modal(self) -> None:
self.main_screen.action_exit_modal()
def main(config):
app = ISponsorBlockTVSetup(config)
app.run()

View File

@@ -1,15 +1,15 @@
import asyncio
import json
import sys
from typing import Any, List
import pyytlounge
from aiohttp import ClientSession
from .constants import youtube_client_blacklist
from pyytlounge.wrapper import NotLinkedException, api_base, as_aiter, Dict
from uuid import uuid4
from pyytlounge.api import api_base
from pyytlounge.exceptions import NotLinkedException
from pyytlounge.util import as_aiter
from .constants import youtube_client_blacklist
create_task = asyncio.create_task
@@ -34,6 +34,8 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
self.logger = logger
self.shorts_disconnected = False
self.auto_play = True
self.watchdog_running = False
self.last_event_time = 0
if config:
self.mute_ads = config.mute_ads
self.skip_ads = config.skip_ads
@@ -42,21 +44,58 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
# Ensures that we still are subscribed to the lounge
async def _watchdog(self):
await asyncio.sleep(
35
) # YouTube sends at least a message every 30 seconds (no-op or any other)
"""
Continuous watchdog that monitors for connection health.
If no events are received within the expected timeframe,
it cancels the current subscription.
"""
self.watchdog_running = True
self.last_event_time = asyncio.get_event_loop().time()
try:
self.subscribe_task.cancel()
except BaseException:
pass
while self.watchdog_running:
await asyncio.sleep(10)
current_time = asyncio.get_event_loop().time()
time_since_last_event = current_time - self.last_event_time
# YouTube sends a message at least every 30 seconds
if time_since_last_event > 60:
self.logger.debug(
f"Watchdog triggered: No events for {time_since_last_event:.1f} seconds"
)
# Cancel current subscription
if self.subscribe_task and not self.subscribe_task.done():
self.subscribe_task.cancel()
await asyncio.sleep(1) # Give it time to cancel
except asyncio.CancelledError:
self.logger.debug("Watchdog task cancelled")
self.watchdog_running = False
except BaseException as e:
self.logger.error(f"Watchdog error: {e}")
self.watchdog_running = False
# Subscribe to the lounge and start the watchdog
async def subscribe_monitored(self, callback):
self.callback = callback
try:
# Stop existing watchdog if running
if self.subscribe_task_watchdog and not self.subscribe_task_watchdog.done():
self.watchdog_running = False
self.subscribe_task_watchdog.cancel()
except BaseException:
pass # No watchdog task
try:
await self.subscribe_task_watchdog
except (asyncio.CancelledError, Exception):
pass
# Start new subscription
if self.subscribe_task and not self.subscribe_task.done():
self.subscribe_task.cancel()
try:
await self.subscribe_task
except (asyncio.CancelledError, Exception):
pass
self.subscribe_task = asyncio.create_task(super().subscribe(callback))
self.subscribe_task_watchdog = asyncio.create_task(self._watchdog())
return self.subscribe_task
@@ -65,13 +104,9 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
# skipcq: PY-R1000
def _process_event(self, event_type: str, args: List[Any]):
self.logger.debug(f"process_event({event_type}, {args})")
# (Re)start the watchdog
try:
self.subscribe_task_watchdog.cancel()
except BaseException:
pass
finally:
self.subscribe_task_watchdog = asyncio.create_task(self._watchdog())
# Update last event time for the watchdog
self.last_event_time = asyncio.get_event_loop().time()
# A bunch of events useful to detect ads playing,
# and the next video before it starts playing
# (that way we can get the segments)
@@ -89,7 +124,7 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
create_task(self.mute(False, override=True))
elif event_type == "onAdStateChange":
data = args[0]
if data["adState"] == "0": # Ad is not playing
if data["adState"] == "0" and data["currentTime"] != "0": # Ad is not playing
self.logger.info("Ad has ended, unmuting")
create_task(self.mute(False, override=True))
elif (
@@ -118,7 +153,8 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
if vid_id := data["contentVideoId"]:
self.logger.info(f"Getting segments for next video: {vid_id}")
create_task(self.api_helper.get_segments(vid_id))
elif (
if (
self.skip_ads and data["isSkipEnabled"] == "true"
): # YouTube uses strings for booleans
self.logger.info("Ad can be skipped, skipping")
@@ -205,41 +241,57 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
await self.conn.close()
self.session = web_session
# TODO: Open a PR upstream to specify the connect_body.method
# if this works
def _common_connection_parameters(self) -> Dict[str, Any]:
return {
"name": self.device_name,
"loungeIdToken": self.auth.lounge_id_token,
"SID": self._sid,
"AID": self._last_event_id,
"gsessionid": self._gsession,
"device": "REMOTE_CONTROL",
"app": "ytios-phone-20.15.1",
"VER": "8",
"v": "2",
}
async def connect(self) -> bool:
"""Attempt to connect using the previously set tokens"""
if not self.linked():
raise NotLinkedException("Not linked")
connect_body = {
"app": "web",
"mdx-version": "3",
"name": self.device_name,
"id": self.auth.screen_id,
"device": "REMOTE_CONTROL",
"capabilities": "que,dsdtr,atp,vsp",
"method": "getNowPlaying",
"magnaKey": "cloudPairedDevice",
"ui": "false",
"deviceContext": "user_agent=dunno&window_width_points=&window_height_points=&os_name=android&ms=",
"mdx-version": "3",
"TYPE": "xmlhttp",
"theme": "cl",
"sessionSource": "MDX_SESSION_SOURCE_UNKNOWN",
"connectParams": '{"setStatesParams": "{"playbackSpeed":0}"}',
"RID": "1",
"CVER": "1",
"capabilities": "que,dsdtr,atp,vsp",
"ui": "false",
"app": "ytios-phone-20.15.1",
"pairing_type": "manual",
"VER": "8",
"loungeIdToken": self.auth.lounge_id_token,
"device": "REMOTE_CONTROL",
"name": self.device_name,
}
connect_url = (
f"{api_base}/bc/bind?RID=1&VER=8&CVER=1&auth_failure_option=send_error"
)
connect_url = f"{api_base}/bc/bind"
async with self.session.post(url=connect_url, data=connect_body) as resp:
try:
text = await resp.text()
if resp.status == 401:
self._lounge_token_expired()
if "Connection denied" in text:
self._logger.warning(
"Connection denied, attempting to circumvent the issue"
)
await self.connect_as_screen()
# self._lounge_token_expired()
return False
if resp.status != 200:
self._logger.warning(
"Unknown reply to connect %i %s", resp.status, resp.reason
)
self._logger.warning("Unknown reply to connect %i %s", resp.status, resp.reason)
return False
lines = text.splitlines()
async for events in self._parse_event_chunks(as_aiter(lines)):
@@ -252,4 +304,46 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
resp.status,
resp.reason,
)
raise
raise
async def connect_as_screen(self) -> bool:
"""Attempt to connect using the previously set tokens"""
if not self.linked():
raise NotLinkedException("Not linked")
connect_body = {
"id": str(uuid4()),
"mdx-version": "3",
"TYPE": "xmlhttp",
"theme": "cl",
"sessionSource": "MDX_SESSION_SOURCE_UNKNOWN",
"connectParams": '{"setStatesParams": "{"playbackSpeed":0}"}',
"sessionNonce": str(uuid4()),
"RID": "1",
"CVER": "1",
"capabilities": "que,dsdtr,atp,vsp",
"ui": "false",
"app": "ytios-phone-20.15.1",
"pairing_type": "manual",
"VER": "8",
"loungeIdToken": self.auth.lounge_id_token,
"device": "LOUNGE_SCREEN",
"name": self.device_name,
}
connect_url = f"{api_base}/bc/bind"
async with self.session.post(url=connect_url, data=connect_body) as resp:
try:
await resp.text()
self.logger.error(
"Connected as screen: please force close the app on the device for iSponsorBlockTV to work properly"
)
self.logger.warn("Exiting in 5 seconds")
await asyncio.sleep(5)
sys.exit(0)
except:
self._logger.exception(
"Handle connect failed, status %s reason %s",
resp.status,
resp.reason,
)
raise