diff --git a/.github/workflows/build_docker_images.yml b/.github/workflows/build_docker_images.yml index ba8d1fc..8bbb573 100644 --- a/.github/workflows/build_docker_images.yml +++ b/.github/workflows/build_docker_images.yml @@ -25,12 +25,12 @@ jobs: steps: # Get the repository's code - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Generate docker tags - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ghcr.io/dmunozv04/isponsorblocktv, dmunozv04/isponsorblocktv tags: | @@ -42,29 +42,29 @@ jobs: # https://github.com/docker/setup-qemu-action - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/setup-buildx-action - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64, linux/arm64, linux/arm/v7 diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index 43c9298..180d5b9 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -22,9 +22,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies diff --git a/.github/workflows/update_docker_readme.yml b/.github/workflows/update_docker_readme.yml index ccf6c37..12a3e62 100644 --- a/.github/workflows/update_docker_readme.yml +++ b/.github/workflows/update_docker_readme.yml @@ -22,7 +22,7 @@ jobs: # Update description - name: Update repo description - uses: peter-evans/dockerhub-description@v3 + uses: peter-evans/dockerhub-description@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile index 5947838..e2067f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,12 +16,12 @@ COPY requirements.txt . RUN apk add --no-cache gcc musl-dev && \ pip install --upgrade pip wheel && \ - pip install --user -r requirements.txt && \ + pip install -r requirements.txt && \ pip uninstall -y pip wheel && \ apk del gcc musl-dev && \ - python3 -m compileall -b -f /root/.local/lib/python3.11/site-packages && \ - find /root/.local/lib/python3.11/site-packages -name "*.py" -type f -delete && \ - find /root/.local/lib/python3.11/ -name "__pycache__" -type d -exec rm -rf {} + + python3 -m compileall -b -f /usr/local/lib/python3.11/site-packages && \ + find /usr/local/lib/python3.11/site-packages -name "*.py" -type f -delete && \ + find /usr/local/lib/python3.11/ -name "__pycache__" -type d -exec rm -rf {} + FROM base @@ -29,7 +29,7 @@ ENV PIP_NO_CACHE_DIR=off iSPBTV_docker=True iSPBTV_data_dir=data TERM=xterm-256c COPY requirements.txt . -COPY --from=dep_installer /root/.local /root/.local +COPY --from=dep_installer /usr/local /usr/local WORKDIR /app diff --git a/pyproject.toml b/pyproject.toml index de6e83e..cd9e702 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "iSponsorBlockTV" -version = "2.0.6" +version = "2.0.8" authors = [ {"name" = "dmunozv04"} ] diff --git a/requirements.txt b/requirements.txt index 293b2ae..e30c014 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -aiohttp==3.9.0 +aiohttp==3.9.5 appdirs==1.4.4 argparse==1.4.0 async-cache==1.1.1 -pyytlounge==1.6.3 -rich==13.6.0 +pyytlounge==2.0.0 +rich==13.7.1 ssdp==1.3.0 -textual==0.40.0 +textual==0.58.0 textual-slider==0.1.1 xmltodict==0.13.0 diff --git a/src/iSponsorBlockTV/config_setup.py b/src/iSponsorBlockTV/config_setup.py index 393d712..7e223f2 100644 --- a/src/iSponsorBlockTV/config_setup.py +++ b/src/iSponsorBlockTV/config_setup.py @@ -5,9 +5,11 @@ import aiohttp from . import api_helpers, ytlounge -async def pair_device(): +async def pair_device(web_session): try: - lounge_controller = ytlounge.YtLoungeApi("iSponsorBlockTV") + lounge_controller = ytlounge.YtLoungeApi( + "iSponsorBlockTV", web_session=web_session + ) pairing_code = input( "Enter pairing code (found in Settings - Link with TV code): " ) @@ -33,6 +35,7 @@ async def pair_device(): def main(config, debug: bool) -> None: print("Welcome to the iSponsorBlockTV cli setup wizard") loop = asyncio.get_event_loop_policy().get_event_loop() + web_session = aiohttp.ClientSession() if debug: loop.set_debug(True) asyncio.set_event_loop(loop) @@ -52,7 +55,7 @@ def main(config, debug: bool) -> None: del config["atvs"] devices = config.devices while not input(f"Paired with {len(devices)} Device(s). Add more? (y/n) ") == "n": - task = loop.create_task(pair_device()) + task = loop.create_task(pair_device(web_session)) loop.run_until_complete(task) device = task.result() if device: @@ -112,7 +115,6 @@ def main(config, debug: bool) -> None: " otherwise the program will fail to start.\nYou can add one by" " re-running this setup wizard." ) - web_session = aiohttp.ClientSession() api_helper = api_helpers.ApiHelper(config, web_session) while True: channel_info = {} @@ -152,7 +154,6 @@ def main(config, debug: bool) -> None: channel_info["name"] = results[int(choice)][1] channel_whitelist.append(channel_info) # Close web session asynchronously - loop.run_until_complete(web_session.close()) config.channel_whitelist = channel_whitelist @@ -164,11 +165,7 @@ def main(config, debug: bool) -> None: == "n" ) - config.auto_play = ( - not input( - "Do you want to enable autoplay? (y/n) " - ) - == "n" - ) + config.auto_play = not input("Do you want to enable autoplay? (y/n) ") == "n" print("Config finished") config.save() + loop.run_until_complete(web_session.close()) diff --git a/src/iSponsorBlockTV/main.py b/src/iSponsorBlockTV/main.py index 8cc6e9d..bb8f1a2 100644 --- a/src/iSponsorBlockTV/main.py +++ b/src/iSponsorBlockTV/main.py @@ -10,13 +10,14 @@ from . import api_helpers, ytlounge class DeviceListener: - def __init__(self, api_helper, config, device, debug: bool): + def __init__(self, api_helper, config, device, debug: bool, web_session): self.task: Optional[asyncio.Task] = None self.api_helper = api_helper self.offset = device.offset self.name = device.name self.cancelled = False self.logger = logging.getLogger(f"iSponsorBlockTV-{device.screen_id}") + self.web_session = web_session if debug: self.logger.setLevel(logging.DEBUG) else: @@ -28,7 +29,7 @@ class DeviceListener: self.logger.addHandler(sh) self.logger.info(f"Starting device") self.lounge_controller = ytlounge.YtLoungeApi( - device.screen_id, config, api_helper, self.logger + device.screen_id, config, api_helper, self.logger, self.web_session ) # Ensures that we have a valid auth token @@ -51,13 +52,13 @@ class DeviceListener: # Main subscription loop async def loop(self): lounge_controller = self.lounge_controller - while not lounge_controller.linked(): - try: - self.logger.debug("Refreshing auth") - await lounge_controller.refresh_auth() - except: - await asyncio.sleep(10) while not self.cancelled: + while not lounge_controller.linked(): + try: + self.logger.debug("Refreshing auth") + await lounge_controller.refresh_auth() + except: + await asyncio.sleep(10) while not (await self.is_available()) and not self.cancelled: await asyncio.sleep(10) try: @@ -155,7 +156,7 @@ def main(config, debug): web_session = aiohttp.ClientSession(loop=loop, connector=tcp_connector) api_helper = api_helpers.ApiHelper(config, web_session) for i in config.devices: - device = DeviceListener(api_helper, config, i, debug) + device = DeviceListener(api_helper, config, i, debug, web_session) devices.append(device) tasks.append(loop.create_task(device.loop())) tasks.append(loop.create_task(device.refresh_auth_loop())) @@ -165,3 +166,5 @@ def main(config, debug): print("Cancelling tasks and exiting...") loop.run_until_complete(finish(devices)) loop.run_until_complete(web_session.close()) + loop.run_until_complete(tcp_connector.close()) + loop.close() diff --git a/src/iSponsorBlockTV/setup_wizard.py b/src/iSponsorBlockTV/setup_wizard.py index ede8bee..083d062 100644 --- a/src/iSponsorBlockTV/setup_wizard.py +++ b/src/iSponsorBlockTV/setup_wizard.py @@ -234,8 +234,8 @@ class AddDevice(ModalWithClickExit): def __init__(self, config, **kwargs) -> None: super().__init__(**kwargs) self.config = config - web_session = aiohttp.ClientSession() - self.api_helper = api_helpers.ApiHelper(config, web_session) + self.web_session = aiohttp.ClientSession() + self.api_helper = api_helpers.ApiHelper(config, self.web_session) self.devices_discovered_dial = [] def compose(self) -> ComposeResult: @@ -336,7 +336,9 @@ class AddDevice(ModalWithClickExit): @on(Button.Pressed, "#add-device-pin-add-button") async def handle_add_device_pin(self) -> None: self.query_one("#add-device-pin-add-button").disabled = True - lounge_controller = ytlounge.YtLoungeApi("iSponsorBlockTV") + lounge_controller = ytlounge.YtLoungeApi( + "iSponsorBlockTV", web_session=self.web_session + ) pairing_code = self.query_one("#pairing-code-input").value pairing_code = int( pairing_code.replace("-", "").replace(" ", "") @@ -858,9 +860,7 @@ class AutoPlayManager(Vertical): def compose(self) -> ComposeResult: yield Label("Autoplay", classes="title") yield Label( - ( - "This feature allows you to enable/disable autoplay" - ), + "This feature allows you to enable/disable autoplay", classes="subtitle", id="autoplay-subtitle", ) diff --git a/src/iSponsorBlockTV/ytlounge.py b/src/iSponsorBlockTV/ytlounge.py index 430021e..ce5f398 100644 --- a/src/iSponsorBlockTV/ytlounge.py +++ b/src/iSponsorBlockTV/ytlounge.py @@ -2,6 +2,7 @@ import asyncio import json import pyytlounge +from aiohttp import ClientSession from .constants import youtube_client_blacklist @@ -9,8 +10,17 @@ create_task = asyncio.create_task class YtLoungeApi(pyytlounge.YtLoungeApi): - def __init__(self, screen_id, config=None, api_helper=None, logger=None): + def __init__( + self, + screen_id, + config=None, + api_helper=None, + logger=None, + web_session: ClientSession = None, + ): super().__init__("iSponsorBlockTV", logger=logger) + if web_session is not None: + self.session = web_session # And use the one we passed self.auth.screen_id = screen_id self.auth.lounge_id_token = None self.api_helper = api_helper