mirror of
https://github.com/dmunozv04/iSponsorBlockTV.git
synced 2025-12-22 15:38:27 +03:00
Compare commits
1 Commits
v2
...
watchdog-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30cac71820 |
@@ -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"]
|
||||||
|
|||||||
@@ -20,6 +20,5 @@
|
|||||||
{"id": "",
|
{"id": "",
|
||||||
"name": ""
|
"name": ""
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"use_proxy": false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "iSponsorBlockTV"
|
name = "iSponsorBlockTV"
|
||||||
version = "2.6.0"
|
version = "2.5.3"
|
||||||
authors = [
|
authors = [
|
||||||
{"name" = "dmunozv04"}
|
{"name" = "dmunozv04"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -383,9 +383,3 @@ MigrationScreen {
|
|||||||
padding: 1;
|
padding: 1;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Use Proxy */
|
|
||||||
#useproxy-container{
|
|
||||||
padding: 1;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user