Compare commits

...

26 Commits

Author SHA1 Message Date
dmunozv04
d17e59bf0d bump version 2024-07-07 17:36:01 +02:00
David
205191f442 Merge pull request #150 from ryankupk/update-cli
Refactor CLI setup script, add prompts for muting and skipping native youtube ads
2024-06-21 17:08:47 +02:00
dmunozv04
810cd5eec3 change default options for autoplay and reporting, and mark default option 2024-06-21 17:08:15 +02:00
pre-commit-ci[bot]
e2ace8629f [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-06-21 15:01:56 +00:00
dmunozv04
e54ead26d2 Merge remote-tracking branch 'origin' into pr/ryankupk/150 2024-06-21 17:01:45 +02:00
David
49fba2f28f Merge pull request #161 from Ravioli8235/autoplay
Implement autoplay on/off toggle
2024-06-21 16:56:57 +02:00
pre-commit-ci[bot]
b1333a2f61 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-06-21 14:29:35 +00:00
dmunozv04
cfef219d32 fix autoplay padding 2024-06-21 16:29:17 +02:00
Ravioli8235
338e0479ba Merge remote-tracking branch 'origin/autoplay' into autoplay 2024-06-21 15:58:28 +02:00
Ravioli8235
bfefa94a7b fix panel fill 2024-06-21 15:58:15 +02:00
David
783e3d4240 Merge branch 'main' into autoplay 2024-06-17 14:55:07 +02:00
David
015f5a79c9 Merge pull request #168 from Shraymonks/fix-docker-root
Fix deps permissions in docker build
2024-06-16 14:22:25 +02:00
Raymond Ha
dc72db0609 Fix deps permissions in docker build 2024-06-05 16:18:10 -07:00
David
8de38cc92b Merge pull request #167 from dmunozv04/update-actions
Update actions versions
2024-06-05 19:29:42 +02:00
dmunozv04
94ba642af1 update actions versions to latest 2024-06-05 19:27:42 +02:00
dmunozv04
6e09db9994 bump version 2024-05-31 15:35:16 +02:00
David
b56d7443d1 Merge pull request #163 from dmunozv04/refresh-auth-after-disconnect
Refresh auth after every disconnect
2024-05-31 15:34:40 +02:00
dmunozv04
e92ba897c4 Refresh auth after every disconnect, ensuring there's a fresh token. Fixes Shorts aren't handled anymore #162
and maybe changes something related to Disable Autoplay #82
2024-05-31 15:26:58 +02:00
dmunozv04
5214190fd0 revert last commit 2024-05-30 12:35:59 +02:00
David
d3341009a6 Merge branch 'main' into autoplay 2024-05-30 12:27:47 +02:00
dmunozv04
adc0f5b95d enable building PR docker images 2024-05-30 12:26:55 +02:00
David
5dbd16ddd5 Merge branch 'main' into autoplay 2024-05-30 09:05:18 +02:00
pre-commit-ci[bot]
faa0379b89 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-05-30 07:03:27 +00:00
Ravioli8235
fb3ed6b39a Implement autoplay 2024-05-30 08:53:35 +02:00
pre-commit-ci[bot]
865f5469a2 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-05-03 16:24:21 +00:00
Ryan Kupka
daa7026221 Refactor CLI setup script, add prompts for muting and skipping native youtube ads 2024-05-03 10:18:16 -06:00
12 changed files with 258 additions and 191 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -12,6 +12,7 @@
"skip_count_tracking": true,
"mute_ads": true,
"skip_ads": true,
"autoplay": true,
"apikey": "",
"channel_whitelist": [
{"id": "",

View File

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

View File

@@ -1,169 +1,195 @@
import asyncio
import aiohttp
from . import api_helpers, ytlounge
async def pair_device(web_session):
try:
lounge_controller = ytlounge.YtLoungeApi(
"iSponsorBlockTV", web_session=web_session
)
pairing_code = input(
"Enter pairing code (found in Settings - Link with TV code): "
)
pairing_code = int(
pairing_code.replace("-", "").replace(" ", "")
) # remove dashes and spaces
print("Pairing...")
paired = await lounge_controller.pair(pairing_code)
if not paired:
print("Failed to pair device")
return
device = {
"screen_id": lounge_controller.auth.screen_id,
"name": lounge_controller.screen_name,
}
print(f"Paired device: {device['name']}")
return device
except Exception as e:
print(f"Failed to pair device: {e}")
return
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)
if hasattr(config, "atvs"):
print(
"The atvs config option is deprecated and has stopped working. Please read"
" this for more information on how to upgrade to V2:"
" \nhttps://github.com/dmunozv04/iSponsorBlockTV/wiki/Migrate-from-V1-to-V2"
)
if (
input(
"Do you want to remove the legacy 'atvs' entry (the app won't start"
" with it present)? (y/n) "
)
== "y"
):
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(web_session))
loop.run_until_complete(task)
device = task.result()
if device:
devices.append(device)
config.devices = devices
apikey = config.apikey
if apikey:
if input("API key already specified. Change it? (y/n) ") == "y":
apikey = input("Enter your API key: ")
else:
if (
input(
"API key only needed for the channel whitelist function. Add it? (y/n) "
)
== "y"
):
print(
"Get youtube apikey here:"
" https://developers.google.com/youtube/registering_an_application"
)
apikey = input("Enter your API key: ")
config.apikey = apikey
skip_categories = config.skip_categories
if skip_categories:
if input("Skip categories already specified. Change them? (y/n) ") == "y":
categories = input(
"Enter skip categories (space or comma sepparated) Options: [sponsor"
" selfpromo exclusive_access interaction poi_highlight intro outro"
" preview filler music_offtopic]:\n"
)
skip_categories = categories.replace(",", " ").split(" ")
skip_categories = [
x for x in skip_categories if x != ""
] # Remove empty strings
else:
categories = input(
"Enter skip categories (space or comma sepparated) Options: [sponsor,"
" selfpromo, exclusive_access, interaction, poi_highlight, intro, outro,"
" preview, filler, music_offtopic:\n"
)
skip_categories = categories.replace(",", " ").split(" ")
skip_categories = [
x for x in skip_categories if x != ""
] # Remove empty strings
config.skip_categories = skip_categories
channel_whitelist = config.channel_whitelist
if (
input("Do you want to whitelist any channels from being ad-blocked? (y/n) ")
== "y"
):
if not apikey:
print(
"WARNING: You need to specify an API key to use this function,"
" otherwise the program will fail to start.\nYou can add one by"
" re-running this setup wizard."
)
api_helper = api_helpers.ApiHelper(config, web_session)
while True:
channel_info = {}
channel = input('Enter a channel name or "/exit" to exit: ')
if channel == "/exit":
break
task = loop.create_task(
api_helper.search_channels(channel, apikey, web_session)
)
loop.run_until_complete(task)
results = task.result()
if len(results) == 0:
print("No channels found")
continue
for i, item in enumerate(results):
print(f"{i}: {item[1]} - Subs: {item[2]}")
print("5: Enter a custom channel ID")
print("6: Go back")
choice = -1
choice = input("Select one option of the above [0-6]: ")
while choice not in [str(x) for x in range(7)]:
print("Invalid choice")
choice = input("Select one option of the above [0-6]: ")
if choice == "5":
channel_info["id"] = input("Enter a channel ID: ")
channel_info["name"] = input("Enter the channel name: ")
channel_whitelist.append(channel_info)
continue
if choice == "6":
continue
channel_info["id"] = results[int(choice)][0]
channel_info["name"] = results[int(choice)][1]
channel_whitelist.append(channel_info)
# Close web session asynchronously
config.channel_whitelist = channel_whitelist
config.skip_count_tracking = (
not input(
"Do you want to report skipped segments to sponsorblock. Only the segment"
" UUID will be sent? (y/n) "
)
== "n"
)
print("Config finished")
config.save()
loop.run_until_complete(web_session.close())
import asyncio
import aiohttp
from . import api_helpers, ytlounge
# Constants for user input prompts
ATVS_REMOVAL_PROMPT = (
"Do you want to remove the legacy 'atvs' entry (the app won't start"
" with it present)? (y/N) "
)
PAIRING_CODE_PROMPT = "Enter pairing code (found in Settings - Link with TV code): "
ADD_MORE_DEVICES_PROMPT = "Paired with {num_devices} Device(s). Add more? (y/N) "
CHANGE_API_KEY_PROMPT = "API key already specified. Change it? (y/N) "
ADD_API_KEY_PROMPT = (
"API key only needed for the channel whitelist function. Add it? (y/N) "
)
ENTER_API_KEY_PROMPT = "Enter your API key: "
CHANGE_SKIP_CATEGORIES_PROMPT = "Skip categories already specified. Change them? (y/N) "
ENTER_SKIP_CATEGORIES_PROMPT = (
"Enter skip categories (space or comma sepparated) Options: [sponsor,"
" selfpromo, exclusive_access, interaction, poi_highlight, intro, outro,"
" preview, filler, music_offtopic]:\n"
)
WHITELIST_CHANNELS_PROMPT = (
"Do you want to whitelist any channels from being ad-blocked? (y/N) "
)
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: "
REPORT_SKIPPED_SEGMENTS_PROMPT = (
"Do you want to report skipped segments to sponsorblock. Only the segment"
" UUID will be sent? (Y/n) "
)
MUTE_ADS_PROMPT = "Do you want to mute native YouTube ads automatically? (y/N) "
SKIP_ADS_PROMPT = "Do you want to skip native YouTube ads automatically? (y/N) "
AUTOPLAY_PROMPT = "Do you want to enable autoplay? (Y/n) "
def get_yn_input(prompt):
while choice := input(prompt):
if choice.lower() in ["y", "n"]:
return choice.lower()
print("Invalid input. Please enter 'y' or 'n'.")
async def pair_device():
try:
lounge_controller = ytlounge.YtLoungeApi("iSponsorBlockTV")
pairing_code = input(PAIRING_CODE_PROMPT)
pairing_code = int(
pairing_code.replace("-", "").replace(" ", "")
) # remove dashes and spaces
print("Pairing...")
paired = await lounge_controller.pair(pairing_code)
if not paired:
print("Failed to pair device")
return
device = {
"screen_id": lounge_controller.auth.screen_id,
"name": lounge_controller.screen_name,
}
print(f"Paired device: {device['name']}")
return device
except Exception as e:
print(f"Failed to pair device: {e}")
return
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)
if hasattr(config, "atvs"):
print(
"The atvs config option is deprecated and has stopped working. Please read"
" this for more information on how to upgrade to V2:"
" \nhttps://github.com/dmunozv04/iSponsorBlockTV/wiki/Migrate-from-V1-to-V2"
)
choice = get_yn_input(ATVS_REMOVAL_PROMPT)
if choice == "y":
del config["atvs"]
devices = config.devices
choice = get_yn_input(ADD_MORE_DEVICES_PROMPT.format(num_devices=len(devices)))
while choice == "y":
task = loop.create_task(pair_device())
loop.run_until_complete(task)
device = task.result()
if device:
devices.append(device)
choice = get_yn_input(ADD_MORE_DEVICES_PROMPT.format(num_devices=len(devices)))
config.devices = devices
apikey = config.apikey
if apikey:
choice = get_yn_input(CHANGE_API_KEY_PROMPT)
if choice == "y":
apikey = input(ENTER_API_KEY_PROMPT)
else:
choice = get_yn_input(ADD_API_KEY_PROMPT)
if choice == "y":
print(
"Get youtube apikey here:"
" https://developers.google.com/youtube/registering_an_application"
)
apikey = input(ENTER_API_KEY_PROMPT)
config.apikey = apikey
skip_categories = config.skip_categories
if skip_categories:
choice = get_yn_input(CHANGE_SKIP_CATEGORIES_PROMPT)
if choice == "y":
categories = input(ENTER_SKIP_CATEGORIES_PROMPT)
skip_categories = categories.replace(",", " ").split(" ")
skip_categories = [
x for x in skip_categories if x != ""
] # Remove empty strings
else:
categories = input(ENTER_SKIP_CATEGORIES_PROMPT)
skip_categories = categories.replace(",", " ").split(" ")
skip_categories = [
x for x in skip_categories if x != ""
] # Remove empty strings
config.skip_categories = skip_categories
channel_whitelist = config.channel_whitelist
choice = get_yn_input(WHITELIST_CHANNELS_PROMPT)
if choice == "y":
if not apikey:
print(
"WARNING: You need to specify an API key to use this function,"
" otherwise the program will fail to start.\nYou can add one by"
" re-running this setup wizard."
)
api_helper = api_helpers.ApiHelper(config, web_session)
while True:
channel_info = {}
channel = input(SEARCH_CHANNEL_PROMPT)
if channel == "/exit":
break
task = loop.create_task(
api_helper.search_channels(channel, apikey, web_session)
)
loop.run_until_complete(task)
results = task.result()
if len(results) == 0:
print("No channels found")
continue
for i, item in enumerate(results):
print(f"{i}: {item[1]} - Subs: {item[2]}")
print("5: Enter a custom channel ID")
print("6: Go back")
while choice := input(SELECT_CHANNEL_PROMPT):
if choice in [str(x) for x in range(7)]:
break
print("Invalid choice")
if choice == "5":
channel_info["id"] = input(ENTER_CHANNEL_ID_PROMPT)
channel_info["name"] = input(ENTER_CUSTOM_CHANNEL_NAME_PROMPT)
channel_whitelist.append(channel_info)
continue
if choice == "6":
continue
channel_info["id"] = results[int(choice)][0]
channel_info["name"] = results[int(choice)][1]
channel_whitelist.append(channel_info)
# Close web session asynchronously
config.channel_whitelist = channel_whitelist
choice = get_yn_input(REPORT_SKIPPED_SEGMENTS_PROMPT)
config.skip_count_tracking = choice != "n"
choice = get_yn_input(MUTE_ADS_PROMPT)
config.mute_ads = choice == "y"
choice = get_yn_input(SKIP_ADS_PROMPT)
config.skip_ads = choice == "y"
choice = get_yn_input(AUTOPLAY_PROMPT)
config.auto_play = choice != "n"
print("Config finished")
config.save()
loop.run_until_complete(web_session.close())

View File

@@ -41,6 +41,7 @@ class Config:
self.skip_count_tracking = True
self.mute_ads = False
self.skip_ads = False
self.auto_play = True
self.__load()
def validate(self):

View File

@@ -52,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:

View File

@@ -363,3 +363,9 @@ MigrationScreen {
width: 1fr;
content-align: center middle;
}
/* Autoplay */
#autoplay-container{
padding: 1;
height: auto;
}

View File

@@ -850,6 +850,32 @@ class ChannelWhitelistManager(Vertical):
self.app.push_screen(AddChannel(self.config), callback=self.new_channel)
class AutoPlayManager(Vertical):
"""Manager for autoplay, allows enabling/disabling autoplay."""
def __init__(self, config, **kwargs) -> None:
super().__init__(**kwargs)
self.config = config
def compose(self) -> ComposeResult:
yield Label("Autoplay", classes="title")
yield Label(
"This feature allows you to enable/disable autoplay",
classes="subtitle",
id="autoplay-subtitle",
)
with Horizontal(id="autoplay-container"):
yield Checkbox(
value=self.config.auto_play,
id="autoplay-switch",
label="Enable autoplay",
)
@on(Checkbox.Changed, "#autoplay-switch")
def changed_skip(self, event: Checkbox.Changed):
self.config.auto_play = event.checkbox.value
class ISponsorBlockTVSetupMainScreen(Screen):
"""Making this a separate screen to avoid a bug: https://github.com/Textualize/textual/issues/3221"""
@@ -886,6 +912,9 @@ class ISponsorBlockTVSetupMainScreen(Screen):
yield ApiKeyManager(
config=self.config, id="api-key-manager", classes="container"
)
yield AutoPlayManager(
config=self.config, id="autoplay-manager", classes="container"
)
def on_mount(self) -> None:
if self.check_for_old_config_entries():

View File

@@ -30,9 +30,11 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
self.callback = None
self.logger = logger
self.shorts_disconnected = False
self.auto_play = True
if config:
self.mute_ads = config.mute_ads
self.skip_ads = config.skip_ads
self.auto_play = config.auto_play
# Ensures that we still are subscribed to the lounge
async def _watchdog(self):
@@ -146,6 +148,8 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
data = args[0]
if data["reason"] == "disconnectedByUserScreenInitiated": # Short playing?
self.shorts_disconnected = True
elif event_type == "onAutoplayModeChanged":
create_task(self.set_auto_play_mode(self.auto_play))
super()._process_event(event_id, event_type, args)