Compare commits

..

1 Commits

Author SHA1 Message Date
dmunozv04
30cac71820 Fix watchdog triggering too much 2025-07-04 19:14:08 +02:00
10 changed files with 54 additions and 82 deletions

View File

@@ -19,13 +19,13 @@ repos:
- id: mixed-line-ending # replaces or checks mixed line ending - id: mixed-line-ending # replaces or checks mixed line ending
- id: trailing-whitespace # checks for trailing whitespace - id: trailing-whitespace # checks for trailing whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7 rev: v0.11.9
hooks: hooks:
- id: ruff - id: ruff
args: [ --fix, --exit-non-zero-on-fix ] args: [ --fix, --exit-non-zero-on-fix ]
- id: ruff-format - id: ruff-format
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.45.0 rev: v0.44.0
hooks: hooks:
- id: markdownlint - id: markdownlint
args: ["--fix"] args: ["--fix"]

View File

@@ -20,6 +20,5 @@
{"id": "", {"id": "",
"name": "" "name": ""
} }
], ]
"use_proxy": false
} }

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ import aiohttp
from . import api_helpers, ytlounge from . import api_helpers, ytlounge
# Constants for user input prompts # Constants for user input prompts
USE_PROXY_PROMPT = "Do you want to use system-wide proxy? (y/N)"
ATVS_REMOVAL_PROMPT = ( ATVS_REMOVAL_PROMPT = (
"Do you want to remove the legacy 'atvs' entry (the app won't start with it present)? (y/N) " "Do you want to remove the legacy 'atvs' entry (the app won't start with it present)? (y/N) "
) )
@@ -46,8 +45,8 @@ def get_yn_input(prompt):
return None return None
async def create_web_session(use_proxy): async def create_web_session():
return aiohttp.ClientSession(trust_env=use_proxy) return aiohttp.ClientSession()
async def pair_device(web_session: aiohttp.ClientSession): async def pair_device(web_session: aiohttp.ClientSession):
@@ -76,12 +75,8 @@ async def pair_device(web_session: aiohttp.ClientSession):
def main(config, debug: bool) -> None: def main(config, debug: bool) -> None:
print("Welcome to the iSponsorBlockTV cli setup wizard") 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() loop = asyncio.get_event_loop_policy().get_event_loop()
web_session = loop.run_until_complete(create_web_session(config.use_proxy)) web_session = loop.run_until_complete(create_web_session())
if debug: if debug:
loop.set_debug(True) loop.set_debug(True)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)

View File

@@ -44,7 +44,6 @@ class Config:
self.minimum_skip_length = 1 self.minimum_skip_length = 1
self.auto_play = True self.auto_play = True
self.join_name = "iSponsorBlockTV" self.join_name = "iSponsorBlockTV"
self.use_proxy = False
self.__load() self.__load()
def validate(self): def validate(self):

View File

@@ -172,11 +172,9 @@ async def main_async(config, debug, http_tracing):
trace_config.on_response_chunk_received.append(tracer.on_response_chunk_received) 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_end.append(tracer.on_request_end)
trace_config.on_request_exception.append(tracer.on_request_exception) trace_config.on_request_exception.append(tracer.on_request_exception)
web_session = aiohttp.ClientSession( web_session = aiohttp.ClientSession(connector=tcp_connector, trace_configs=[trace_config])
trust_env=config.use_proxy, connector=tcp_connector, trace_configs=[trace_config]
)
else: else:
web_session = aiohttp.ClientSession(trust_env=config.use_proxy, connector=tcp_connector) web_session = aiohttp.ClientSession(connector=tcp_connector)
api_helper = api_helpers.ApiHelper(config, web_session) api_helper = api_helpers.ApiHelper(config, web_session)
for i in config.devices: for i in config.devices:

View File

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

View File

@@ -13,7 +13,6 @@ from textual.containers import (
ScrollableContainer, ScrollableContainer,
Vertical, Vertical,
) )
from textual.css.query import NoMatches
from textual.events import Click from textual.events import Click
from textual.screen import Screen from textual.screen import Screen
from textual.validation import Function from textual.validation import Function
@@ -234,7 +233,7 @@ class AddDevice(ModalWithClickExit):
def __init__(self, config, **kwargs) -> None: def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.config = config self.config = config
self.web_session = aiohttp.ClientSession(trust_env=config.use_proxy) self.web_session = aiohttp.ClientSession()
self.api_helper = api_helpers.ApiHelper(config, self.web_session) self.api_helper = api_helpers.ApiHelper(config, self.web_session)
self.devices_discovered_dial = [] self.devices_discovered_dial = []
@@ -302,11 +301,7 @@ class AddDevice(ModalWithClickExit):
async def task_discover_devices(self): async def task_discover_devices(self):
devices_found = await self.api_helper.discover_youtube_devices_dial() devices_found = await self.api_helper.discover_youtube_devices_dial()
try: list_widget: SelectionList = self.query_one("#dial-devices-list")
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() list_widget.clear_options()
if devices_found: if devices_found:
# print(devices_found) # print(devices_found)
@@ -341,7 +336,7 @@ class AddDevice(ModalWithClickExit):
pairing_code = int( pairing_code = int(
pairing_code.replace("-", "").replace(" ", "") pairing_code.replace("-", "").replace(" ", "")
) # remove dashes and spaces ) # remove dashes and spaces
device_name = self.query_one("#device-name-input").value device_name = self.parent.query_one("#device-name-input").value
paired = False paired = False
try: try:
paired = await lounge_controller.pair(pairing_code) paired = await lounge_controller.pair(pairing_code)
@@ -387,7 +382,7 @@ class AddChannel(ModalWithClickExit):
def __init__(self, config, **kwargs) -> None: def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.config = config self.config = config
web_session = aiohttp.ClientSession(trust_env=config.use_proxy) web_session = aiohttp.ClientSession()
self.api_helper = api_helpers.ApiHelper(config, web_session) self.api_helper = api_helpers.ApiHelper(config, web_session)
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
@@ -664,7 +659,7 @@ class ApiKeyManager(Vertical):
@on(Button.Pressed, "#api-key-view") @on(Button.Pressed, "#api-key-view")
def pressed_api_key_view(self, event: Button.Pressed): def pressed_api_key_view(self, event: Button.Pressed):
if "Show" in str(event.button.label): if "Show" in event.button.label:
event.button.label = "Hide key" event.button.label = "Hide key"
self.query_one("#api-key-input").password = False self.query_one("#api-key-input").password = False
else: else:
@@ -825,7 +820,10 @@ class ChannelWhitelistManager(Vertical):
id="channel-whitelist-subtitle", id="channel-whitelist-subtitle",
) )
yield Label( yield Label(
("⚠️ [#FF0000]You need to set your YouTube Api Key in order to use this feature"), (
":warning: [#FF0000]You need to set your YouTube Api Key in order to"
" use this feature"
),
id="warning-no-key", id="warning-no-key",
) )
with Horizontal(id="add-channel-button-container"): with Horizontal(id="add-channel-button-container"):
@@ -892,45 +890,11 @@ class AutoPlayManager(Vertical):
self.config.auto_play = event.checkbox.value self.config.auto_play = event.checkbox.value
class UseProxyManager(Vertical): class ISponsorBlockTVSetupMainScreen(Screen):
"""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" TITLE = "iSponsorBlockTV"
SUB_TITLE = "Setup Wizard" SUB_TITLE = "Setup Wizard"
BINDINGS = [("q,ctrl+c", "exit_modal", "Exit"), ("s", "save", "Save")] BINDINGS = [("q,ctrl+c", "exit_modal", "Exit"), ("s", "save", "Save")]
AUTO_FOCUS = None AUTO_FOCUS = None
CSS_PATH = ( # tcss is the recommended extension for textual css files
"setup-wizard-style.tcss"
)
def __init__(self, config, **kwargs) -> None: def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -962,7 +926,6 @@ class ISponsorBlockTVSetup(App):
) )
yield ApiKeyManager(config=self.config, id="api-key-manager", classes="container") yield ApiKeyManager(config=self.config, id="api-key-manager", classes="container")
yield AutoPlayManager(config=self.config, id="autoplay-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: def on_mount(self) -> None:
if self.check_for_old_config_entries(): if self.check_for_old_config_entries():
@@ -986,13 +949,36 @@ class ISponsorBlockTVSetup(App):
@on(Input.Changed, "#api-key-input") @on(Input.Changed, "#api-key-input")
def changed_api_key(self, event: Input.Changed): def changed_api_key(self, event: Input.Changed):
try: # ChannelWhitelist might not be mounted try: # ChannelWhitelist might not be mounted
self.app.query_one("#warning-no-key").display = bool( # Show if no api key is set and at least one channel is in the whitelist
(not event.input.value) and self.config.channel_whitelist self.app.query_one("#warning-no-key").display = (
) not event.input.value
except NoMatches: ) and self.config.channel_whitelist
except BaseException:
pass 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): def main(config):
app = ISponsorBlockTVSetup(config) app = ISponsorBlockTVSetup(config)
app.run() app.run()

View File

@@ -1,6 +1,7 @@
import asyncio import asyncio
import json import json
import sys import sys
import time
from typing import Any, List from typing import Any, List
import pyytlounge import pyytlounge
@@ -50,12 +51,12 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
it cancels the current subscription. it cancels the current subscription.
""" """
self.watchdog_running = True self.watchdog_running = True
self.last_event_time = asyncio.get_event_loop().time() self.last_event_time = time.time()
try: try:
while self.watchdog_running: while self.watchdog_running:
await asyncio.sleep(10) await asyncio.sleep(10)
current_time = asyncio.get_event_loop().time() current_time = time.time()
time_since_last_event = current_time - self.last_event_time time_since_last_event = current_time - self.last_event_time
# YouTube sends a message at least every 30 seconds # YouTube sends a message at least every 30 seconds
@@ -105,7 +106,7 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
def _process_event(self, event_type: str, args: List[Any]): def _process_event(self, event_type: str, args: List[Any]):
self.logger.debug(f"process_event({event_type}, {args})") self.logger.debug(f"process_event({event_type}, {args})")
# Update last event time for the watchdog # Update last event time for the watchdog
self.last_event_time = asyncio.get_event_loop().time() self.last_event_time = time.time()
# A bunch of events useful to detect ads playing, # A bunch of events useful to detect ads playing,
# and the next video before it starts playing # and the next video before it starts playing