Compare commits

...

33 Commits
v2.5.1 ... v2

Author SHA1 Message Date
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
11 changed files with 236 additions and 54 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.11.9 rev: v0.12.7
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.44.0 rev: v0.45.0
hooks: hooks:
- id: markdownlint - id: markdownlint
args: ["--fix"] args: ["--fix"]

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
aiohttp==3.11.18 aiohttp==3.12.14
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.0.0 rich==14.1.0
ssdp==1.3.0 ssdp==1.3.0
textual==2.1.2 textual==5.3.0
textual-slider==0.2.0 textual-slider==0.2.0
xmltodict==0.14.2 xmltodict==0.14.2
rich_click==1.8.8 rich_click==1.8.9

View File

@@ -5,6 +5,7 @@ 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) "
) )
@@ -45,8 +46,8 @@ def get_yn_input(prompt):
return None return None
async def create_web_session(): async def create_web_session(use_proxy):
return aiohttp.ClientSession() return aiohttp.ClientSession(trust_env=use_proxy)
async def pair_device(web_session: aiohttp.ClientSession): async def pair_device(web_session: aiohttp.ClientSession):
@@ -75,8 +76,12 @@ 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()) web_session = loop.run_until_complete(create_web_session(config.use_proxy))
if debug: if debug:
loop.set_debug(True) loop.set_debug(True)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)

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

@@ -44,6 +44,7 @@ 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):
@@ -131,6 +132,7 @@ class Config:
help="data directory", help="data directory",
) )
@click.option("--debug", is_flag=True, help="debug mode") @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 # legacy commands as arguments
@click.option("--setup", is_flag=True, help="Setup the program graphically", hidden=True) @click.option("--setup", is_flag=True, help="Setup the program graphically", hidden=True)
@click.option( @click.option(
@@ -140,11 +142,12 @@ class Config:
hidden=True, hidden=True,
) )
@click.pass_context @click.pass_context
def cli(ctx, data, debug, setup, setup_cli): def cli(ctx, data, debug, http_tracing, setup, setup_cli):
"""iSponsorblockTV""" """iSponsorblockTV"""
ctx.ensure_object(dict) ctx.ensure_object(dict)
ctx.obj["data_dir"] = data ctx.obj["data_dir"] = data
ctx.obj["debug"] = debug ctx.obj["debug"] = debug
ctx.obj["http_tracing"] = http_tracing
logger = logging.getLogger() logger = logging.getLogger()
ctx.obj["logger"] = logger ctx.obj["logger"] = logger
@@ -189,7 +192,7 @@ def start(ctx):
"""Start the main program""" """Start the main program"""
config = Config(ctx.obj["data_dir"]) config = Config(ctx.obj["data_dir"])
config.validate() 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 # Create fake "self" group to show pyapp options in help menu

View File

@@ -7,6 +7,7 @@ from typing import Optional
import aiohttp import aiohttp
from . import api_helpers, ytlounge from . import api_helpers, ytlounge
from .debug_helpers import AiohttpTracer
class DeviceListener: class DeviceListener:
@@ -153,14 +154,30 @@ def handle_signal(signum, frame):
raise KeyboardInterrupt() 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() loop = asyncio.get_event_loop_policy().get_event_loop()
tasks = [] # Save the tasks so the interpreter doesn't garbage collect them tasks = [] # Save the tasks so the interpreter doesn't garbage collect them
devices = [] # Save the devices to close them later devices = [] # Save the devices to close them later
if debug: if debug:
loop.set_debug(True) loop.set_debug(True)
tcp_connector = aiohttp.TCPConnector(ttl_dns_cache=300) 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) api_helper = api_helpers.ApiHelper(config, web_session)
for i in config.devices: for i in config.devices:
device = DeviceListener(api_helper, config, i, debug, web_session) device = DeviceListener(api_helper, config, i, debug, web_session)
@@ -184,5 +201,5 @@ async def main_async(config, debug):
print("Exited") print("Exited")
def main(config, debug): def main(config, debug, http_tracing):
asyncio.run(main_async(config, debug)) asyncio.run(main_async(config, debug, http_tracing))

View File

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

View File

@@ -13,6 +13,7 @@ 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
@@ -233,7 +234,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() self.web_session = aiohttp.ClientSession(trust_env=config.use_proxy)
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 = []
@@ -301,7 +302,11 @@ 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()
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() list_widget.clear_options()
if devices_found: if devices_found:
# print(devices_found) # print(devices_found)
@@ -336,7 +341,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.parent.query_one("#device-name-input").value device_name = self.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)
@@ -382,7 +387,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() web_session = aiohttp.ClientSession(trust_env=config.use_proxy)
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:
@@ -659,7 +664,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 event.button.label: if "Show" in str(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:
@@ -820,10 +825,7 @@ 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"):
@@ -890,11 +892,45 @@ class AutoPlayManager(Vertical):
self.config.auto_play = event.checkbox.value 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" 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)
@@ -926,6 +962,7 @@ class ISponsorBlockTVSetupMainScreen(Screen):
) )
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():
@@ -949,36 +986,13 @@ class ISponsorBlockTVSetupMainScreen(Screen):
@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
# Show if no api key is set and at least one channel is in the whitelist self.app.query_one("#warning-no-key").display = bool(
self.app.query_one("#warning-no-key").display = ( (not event.input.value) and self.config.channel_whitelist
not event.input.value )
) and self.config.channel_whitelist except NoMatches:
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,10 +1,14 @@
import asyncio import asyncio
import json import json
import sys
from typing import Any, List from typing import Any, List
import pyytlounge import pyytlounge
from aiohttp import ClientSession from aiohttp import ClientSession
from pyytlounge.wrapper import NotLinkedException, api_base, as_aiter, Dict
from uuid import uuid4
from .constants import youtube_client_blacklist from .constants import youtube_client_blacklist
create_task = asyncio.create_task create_task = asyncio.create_task
@@ -236,3 +240,110 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
if self.conn is not None: if self.conn is not None:
await self.conn.close() await self.conn.close()
self.session = web_session self.session = web_session
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 = {
"id": self.auth.screen_id,
"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"
async with self.session.post(url=connect_url, data=connect_body) as resp:
try:
text = await resp.text()
if resp.status == 401:
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)
return False
lines = text.splitlines()
async for events in self._parse_event_chunks(as_aiter(lines)):
self._process_events(events)
self._command_offset = 1
return self.connected()
except:
self._logger.exception(
"Handle connect failed, status %s reason %s",
resp.status,
resp.reason,
)
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