mirror of
https://github.com/dmunozv04/iSponsorBlockTV.git
synced 2025-12-15 00:16:43 +03:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d3b1c6469 | ||
|
|
b7a295b3e2 | ||
|
|
c56cbfe095 | ||
|
|
bde4ecb72f | ||
|
|
167383dea8 | ||
|
|
fb3c40d477 | ||
|
|
cb738965a7 | ||
|
|
9ad335793a | ||
|
|
fd400d077a | ||
|
|
f9c7b58ece | ||
|
|
464baa7c59 | ||
|
|
d9986e52b3 | ||
|
|
547a47b9ec | ||
|
|
87d0e0e32e | ||
|
|
854cb2462f | ||
|
|
662b71fc00 | ||
|
|
fd6b7cb43a | ||
|
|
d17e59bf0d | ||
|
|
5bc6382f89 | ||
|
|
205191f442 | ||
|
|
810cd5eec3 | ||
|
|
e2ace8629f | ||
|
|
e54ead26d2 | ||
|
|
49fba2f28f | ||
|
|
b1333a2f61 | ||
|
|
cfef219d32 | ||
|
|
338e0479ba | ||
|
|
bfefa94a7b | ||
|
|
783e3d4240 | ||
|
|
015f5a79c9 | ||
|
|
dc72db0609 | ||
|
|
8de38cc92b | ||
|
|
94ba642af1 | ||
|
|
d3341009a6 | ||
|
|
5dbd16ddd5 | ||
|
|
faa0379b89 | ||
|
|
fb3ed6b39a | ||
|
|
865f5469a2 | ||
|
|
daa7026221 |
14
.github/workflows/build_docker_images.yml
vendored
14
.github/workflows/build_docker_images.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Get the repository's code
|
# Get the repository's code
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Generate docker tags
|
# Generate docker tags
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/dmunozv04/isponsorblocktv, dmunozv04/isponsorblocktv
|
images: ghcr.io/dmunozv04/isponsorblocktv, dmunozv04/isponsorblocktv
|
||||||
tags: |
|
tags: |
|
||||||
@@ -42,29 +42,29 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/setup-qemu-action
|
# https://github.com/docker/setup-qemu-action
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
||||||
|
|||||||
196
.github/workflows/release.yml
vendored
Normal file
196
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# This workflow will upload a Python Package using Twine when a release is created
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Release Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.11"
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
jobs:
|
||||||
|
build-sdist-and-wheel:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Hatch
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install hatch
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: python -m hatch build
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdist-and-wheel
|
||||||
|
path: dist/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
build-binaries:
|
||||||
|
name: Build binaries for ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
|
needs:
|
||||||
|
- build-sdist-and-wheel
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
# Linux
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
cross: true
|
||||||
|
release_suffix: x86_64-linux
|
||||||
|
- target: aarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
cross: true
|
||||||
|
release_suffix: aarch64-linux
|
||||||
|
# Windows
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
os: windows-2022
|
||||||
|
release_suffix: x86_64-windows
|
||||||
|
# macOS
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-14
|
||||||
|
release_suffix: aarch64-osx
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-12
|
||||||
|
release_suffix: x86_64-osx
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYAPP_PASS_LOCATION: "1"
|
||||||
|
PYAPP_UV_ENABLED: "1"
|
||||||
|
HATCH_BUILD_LOCATION: dist
|
||||||
|
CARGO: cargo
|
||||||
|
CARGO_BUILD_TARGET: ${{ matrix.job.target }}
|
||||||
|
PYAPP_REPO: pyapp # Use local copy of pyapp (needed for cross-compiling)
|
||||||
|
PYAPP_VERSION: v0.23.0
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Clone PyApp
|
||||||
|
run: git clone --depth 1 --branch $PYAPP_VERSION https://github.com/ofek/pyapp $PYAPP_REPO
|
||||||
|
|
||||||
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Hatch
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install hatch
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
if: ${{ !matrix.job.cross }}
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.job.target }}
|
||||||
|
|
||||||
|
- name: Set up cross compiling tools
|
||||||
|
if: matrix.job.cross
|
||||||
|
uses: taiki-e/setup-cross-toolchain-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.job.target}}
|
||||||
|
|
||||||
|
- name: Show toolchain information
|
||||||
|
run: |-
|
||||||
|
rustup toolchain list
|
||||||
|
rustup default
|
||||||
|
rustup -V
|
||||||
|
rustc -V
|
||||||
|
cargo -V
|
||||||
|
hatch --version
|
||||||
|
|
||||||
|
- name: Get artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdist-and-wheel
|
||||||
|
path: ${{ github.workspace }}/dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Build Binary
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |-
|
||||||
|
current_version=$(hatch version)
|
||||||
|
PYAPP_PROJECT_PATH="${{ github.workspace }}/dist/isponsorblocktv-${current_version}-py3-none-any.whl" hatch -v build -t binary
|
||||||
|
|
||||||
|
- name: Rename binary
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: |-
|
||||||
|
mv dist/binary/iSponsorBlockTV* dist/binary/iSponsorBlockTV-${{ matrix.job.release_suffix }}
|
||||||
|
|
||||||
|
- name: Upload built binary package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binaries-${{ matrix.job.release_suffix }}
|
||||||
|
path: dist/binary/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
publish-to-pypi:
|
||||||
|
needs: build-sdist-and-wheel
|
||||||
|
permissions:
|
||||||
|
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||||
|
# only run step if the event is a published release
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdist-and-wheel
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Publish package
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
|
|
||||||
|
publish-to-release:
|
||||||
|
needs:
|
||||||
|
- build-sdist-and-wheel
|
||||||
|
- build-binaries
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
name: Get artifact
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Add assets to release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: dist/*
|
||||||
37
.github/workflows/release_pypi.yml
vendored
37
.github/workflows/release_pypi.yml
vendored
@@ -1,37 +0,0 @@
|
|||||||
# This workflow will upload a Python Package using Twine when a release is created
|
|
||||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
name: Upload Python Package
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install build wheel
|
|
||||||
- name: Build package
|
|
||||||
run: python -m build
|
|
||||||
- name: Publish package
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
2
.github/workflows/update_docker_readme.yml
vendored
2
.github/workflows/update_docker_readme.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
# Update description
|
# Update description
|
||||||
- name: Update repo description
|
- name: Update repo description
|
||||||
uses: peter-evans/dockerhub-description@v3
|
uses: peter-evans/dockerhub-description@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|||||||
16
Dockerfile
16
Dockerfile
@@ -1,7 +1,7 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM python:3.11-alpine3.19 as BASE
|
FROM python:3.11-alpine3.19 AS base
|
||||||
|
|
||||||
FROM base as compiler
|
FROM base AS compiler
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -10,18 +10,18 @@ COPY src .
|
|||||||
RUN python3 -m compileall -b -f . && \
|
RUN python3 -m compileall -b -f . && \
|
||||||
find . -name "*.py" -type f -delete
|
find . -name "*.py" -type f -delete
|
||||||
|
|
||||||
FROM base as DEP_INSTALLER
|
FROM base AS dep_installer
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
RUN apk add --no-cache gcc musl-dev && \
|
RUN apk add --no-cache gcc musl-dev && \
|
||||||
pip install --upgrade pip wheel && \
|
pip install --upgrade pip wheel && \
|
||||||
pip install --user -r requirements.txt && \
|
pip install -r requirements.txt && \
|
||||||
pip uninstall -y pip wheel && \
|
pip uninstall -y pip wheel && \
|
||||||
apk del gcc musl-dev && \
|
apk del gcc musl-dev && \
|
||||||
python3 -m compileall -b -f /root/.local/lib/python3.11/site-packages && \
|
python3 -m compileall -b -f /usr/local/lib/python3.11/site-packages && \
|
||||||
find /root/.local/lib/python3.11/site-packages -name "*.py" -type f -delete && \
|
find /usr/local/lib/python3.11/site-packages -name "*.py" -type f -delete && \
|
||||||
find /root/.local/lib/python3.11/ -name "__pycache__" -type d -exec rm -rf {} +
|
find /usr/local/lib/python3.11/ -name "__pycache__" -type d -exec rm -rf {} +
|
||||||
|
|
||||||
FROM base
|
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 requirements.txt .
|
||||||
|
|
||||||
COPY --from=dep_installer /root/.local /root/.local
|
COPY --from=dep_installer /usr/local /usr/local
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"skip_count_tracking": true,
|
"skip_count_tracking": true,
|
||||||
"mute_ads": true,
|
"mute_ads": true,
|
||||||
"skip_ads": true,
|
"skip_ads": true,
|
||||||
|
"auto_play": true,
|
||||||
"apikey": "",
|
"apikey": "",
|
||||||
"channel_whitelist": [
|
"channel_whitelist": [
|
||||||
{"id": "",
|
{"id": "",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "iSponsorBlockTV"
|
name = "iSponsorBlockTV"
|
||||||
version = "2.0.8"
|
version = "2.1.0"
|
||||||
authors = [
|
authors = [
|
||||||
{"name" = "dmunozv04"}
|
{"name" = "dmunozv04"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ ssdp==1.3.0
|
|||||||
textual==0.58.0
|
textual==0.58.0
|
||||||
textual-slider==0.1.1
|
textual-slider==0.1.1
|
||||||
xmltodict==0.13.0
|
xmltodict==0.13.0
|
||||||
|
rich_click==1.8.3
|
||||||
|
|||||||
@@ -1,169 +1,195 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from . import api_helpers, ytlounge
|
from . import api_helpers, ytlounge
|
||||||
|
|
||||||
|
# Constants for user input prompts
|
||||||
async def pair_device(web_session):
|
ATVS_REMOVAL_PROMPT = (
|
||||||
try:
|
"Do you want to remove the legacy 'atvs' entry (the app won't start"
|
||||||
lounge_controller = ytlounge.YtLoungeApi(
|
" with it present)? (y/N) "
|
||||||
"iSponsorBlockTV", web_session=web_session
|
)
|
||||||
)
|
PAIRING_CODE_PROMPT = "Enter pairing code (found in Settings - Link with TV code): "
|
||||||
pairing_code = input(
|
ADD_MORE_DEVICES_PROMPT = "Paired with {num_devices} Device(s). Add more? (y/N) "
|
||||||
"Enter pairing code (found in Settings - Link with TV code): "
|
CHANGE_API_KEY_PROMPT = "API key already specified. Change it? (y/N) "
|
||||||
)
|
ADD_API_KEY_PROMPT = (
|
||||||
pairing_code = int(
|
"API key only needed for the channel whitelist function. Add it? (y/N) "
|
||||||
pairing_code.replace("-", "").replace(" ", "")
|
)
|
||||||
) # remove dashes and spaces
|
ENTER_API_KEY_PROMPT = "Enter your API key: "
|
||||||
print("Pairing...")
|
CHANGE_SKIP_CATEGORIES_PROMPT = "Skip categories already specified. Change them? (y/N) "
|
||||||
paired = await lounge_controller.pair(pairing_code)
|
ENTER_SKIP_CATEGORIES_PROMPT = (
|
||||||
if not paired:
|
"Enter skip categories (space or comma sepparated) Options: [sponsor,"
|
||||||
print("Failed to pair device")
|
" selfpromo, exclusive_access, interaction, poi_highlight, intro, outro,"
|
||||||
return
|
" preview, filler, music_offtopic]:\n"
|
||||||
device = {
|
)
|
||||||
"screen_id": lounge_controller.auth.screen_id,
|
WHITELIST_CHANNELS_PROMPT = (
|
||||||
"name": lounge_controller.screen_name,
|
"Do you want to whitelist any channels from being ad-blocked? (y/N) "
|
||||||
}
|
)
|
||||||
print(f"Paired device: {device['name']}")
|
SEARCH_CHANNEL_PROMPT = 'Enter a channel name or "/exit" to exit: '
|
||||||
return device
|
SELECT_CHANNEL_PROMPT = "Select one option of the above [0-6]: "
|
||||||
except Exception as e:
|
ENTER_CHANNEL_ID_PROMPT = "Enter a channel ID: "
|
||||||
print(f"Failed to pair device: {e}")
|
ENTER_CUSTOM_CHANNEL_NAME_PROMPT = "Enter the channel name: "
|
||||||
return
|
REPORT_SKIPPED_SEGMENTS_PROMPT = (
|
||||||
|
"Do you want to report skipped segments to sponsorblock. Only the segment"
|
||||||
|
" UUID will be sent? (Y/n) "
|
||||||
def main(config, debug: bool) -> None:
|
)
|
||||||
print("Welcome to the iSponsorBlockTV cli setup wizard")
|
MUTE_ADS_PROMPT = "Do you want to mute native YouTube ads automatically? (y/N) "
|
||||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
SKIP_ADS_PROMPT = "Do you want to skip native YouTube ads automatically? (y/N) "
|
||||||
web_session = aiohttp.ClientSession()
|
AUTOPLAY_PROMPT = "Do you want to enable autoplay? (Y/n) "
|
||||||
if debug:
|
|
||||||
loop.set_debug(True)
|
|
||||||
asyncio.set_event_loop(loop)
|
def get_yn_input(prompt):
|
||||||
if hasattr(config, "atvs"):
|
while choice := input(prompt):
|
||||||
print(
|
if choice.lower() in ["y", "n"]:
|
||||||
"The atvs config option is deprecated and has stopped working. Please read"
|
return choice.lower()
|
||||||
" this for more information on how to upgrade to V2:"
|
print("Invalid input. Please enter 'y' or 'n'.")
|
||||||
" \nhttps://github.com/dmunozv04/iSponsorBlockTV/wiki/Migrate-from-V1-to-V2"
|
|
||||||
)
|
|
||||||
if (
|
async def pair_device():
|
||||||
input(
|
try:
|
||||||
"Do you want to remove the legacy 'atvs' entry (the app won't start"
|
lounge_controller = ytlounge.YtLoungeApi("iSponsorBlockTV")
|
||||||
" with it present)? (y/n) "
|
pairing_code = input(PAIRING_CODE_PROMPT)
|
||||||
)
|
pairing_code = int(
|
||||||
== "y"
|
pairing_code.replace("-", "").replace(" ", "")
|
||||||
):
|
) # remove dashes and spaces
|
||||||
del config["atvs"]
|
print("Pairing...")
|
||||||
devices = config.devices
|
paired = await lounge_controller.pair(pairing_code)
|
||||||
while not input(f"Paired with {len(devices)} Device(s). Add more? (y/n) ") == "n":
|
if not paired:
|
||||||
task = loop.create_task(pair_device(web_session))
|
print("Failed to pair device")
|
||||||
loop.run_until_complete(task)
|
return
|
||||||
device = task.result()
|
device = {
|
||||||
if device:
|
"screen_id": lounge_controller.auth.screen_id,
|
||||||
devices.append(device)
|
"name": lounge_controller.screen_name,
|
||||||
config.devices = devices
|
}
|
||||||
|
print(f"Paired device: {device['name']}")
|
||||||
apikey = config.apikey
|
return device
|
||||||
if apikey:
|
except Exception as e:
|
||||||
if input("API key already specified. Change it? (y/n) ") == "y":
|
print(f"Failed to pair device: {e}")
|
||||||
apikey = input("Enter your API key: ")
|
return
|
||||||
else:
|
|
||||||
if (
|
|
||||||
input(
|
def main(config, debug: bool) -> None:
|
||||||
"API key only needed for the channel whitelist function. Add it? (y/n) "
|
print("Welcome to the iSponsorBlockTV cli setup wizard")
|
||||||
)
|
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||||
== "y"
|
web_session = aiohttp.ClientSession()
|
||||||
):
|
if debug:
|
||||||
print(
|
loop.set_debug(True)
|
||||||
"Get youtube apikey here:"
|
asyncio.set_event_loop(loop)
|
||||||
" https://developers.google.com/youtube/registering_an_application"
|
if hasattr(config, "atvs"):
|
||||||
)
|
print(
|
||||||
apikey = input("Enter your API key: ")
|
"The atvs config option is deprecated and has stopped working. Please read"
|
||||||
config.apikey = apikey
|
" this for more information on how to upgrade to V2:"
|
||||||
|
" \nhttps://github.com/dmunozv04/iSponsorBlockTV/wiki/Migrate-from-V1-to-V2"
|
||||||
skip_categories = config.skip_categories
|
)
|
||||||
if skip_categories:
|
choice = get_yn_input(ATVS_REMOVAL_PROMPT)
|
||||||
if input("Skip categories already specified. Change them? (y/n) ") == "y":
|
if choice == "y":
|
||||||
categories = input(
|
del config["atvs"]
|
||||||
"Enter skip categories (space or comma sepparated) Options: [sponsor"
|
|
||||||
" selfpromo exclusive_access interaction poi_highlight intro outro"
|
devices = config.devices
|
||||||
" preview filler music_offtopic]:\n"
|
choice = get_yn_input(ADD_MORE_DEVICES_PROMPT.format(num_devices=len(devices)))
|
||||||
)
|
while choice == "y":
|
||||||
skip_categories = categories.replace(",", " ").split(" ")
|
task = loop.create_task(pair_device())
|
||||||
skip_categories = [
|
loop.run_until_complete(task)
|
||||||
x for x in skip_categories if x != ""
|
device = task.result()
|
||||||
] # Remove empty strings
|
if device:
|
||||||
else:
|
devices.append(device)
|
||||||
categories = input(
|
choice = get_yn_input(ADD_MORE_DEVICES_PROMPT.format(num_devices=len(devices)))
|
||||||
"Enter skip categories (space or comma sepparated) Options: [sponsor,"
|
config.devices = devices
|
||||||
" selfpromo, exclusive_access, interaction, poi_highlight, intro, outro,"
|
|
||||||
" preview, filler, music_offtopic:\n"
|
apikey = config.apikey
|
||||||
)
|
if apikey:
|
||||||
skip_categories = categories.replace(",", " ").split(" ")
|
choice = get_yn_input(CHANGE_API_KEY_PROMPT)
|
||||||
skip_categories = [
|
if choice == "y":
|
||||||
x for x in skip_categories if x != ""
|
apikey = input(ENTER_API_KEY_PROMPT)
|
||||||
] # Remove empty strings
|
else:
|
||||||
config.skip_categories = skip_categories
|
choice = get_yn_input(ADD_API_KEY_PROMPT)
|
||||||
|
if choice == "y":
|
||||||
channel_whitelist = config.channel_whitelist
|
print(
|
||||||
if (
|
"Get youtube apikey here:"
|
||||||
input("Do you want to whitelist any channels from being ad-blocked? (y/n) ")
|
" https://developers.google.com/youtube/registering_an_application"
|
||||||
== "y"
|
)
|
||||||
):
|
apikey = input(ENTER_API_KEY_PROMPT)
|
||||||
if not apikey:
|
config.apikey = apikey
|
||||||
print(
|
|
||||||
"WARNING: You need to specify an API key to use this function,"
|
skip_categories = config.skip_categories
|
||||||
" otherwise the program will fail to start.\nYou can add one by"
|
if skip_categories:
|
||||||
" re-running this setup wizard."
|
choice = get_yn_input(CHANGE_SKIP_CATEGORIES_PROMPT)
|
||||||
)
|
if choice == "y":
|
||||||
api_helper = api_helpers.ApiHelper(config, web_session)
|
categories = input(ENTER_SKIP_CATEGORIES_PROMPT)
|
||||||
while True:
|
skip_categories = categories.replace(",", " ").split(" ")
|
||||||
channel_info = {}
|
skip_categories = [
|
||||||
channel = input('Enter a channel name or "/exit" to exit: ')
|
x for x in skip_categories if x != ""
|
||||||
if channel == "/exit":
|
] # Remove empty strings
|
||||||
break
|
else:
|
||||||
|
categories = input(ENTER_SKIP_CATEGORIES_PROMPT)
|
||||||
task = loop.create_task(
|
skip_categories = categories.replace(",", " ").split(" ")
|
||||||
api_helper.search_channels(channel, apikey, web_session)
|
skip_categories = [
|
||||||
)
|
x for x in skip_categories if x != ""
|
||||||
loop.run_until_complete(task)
|
] # Remove empty strings
|
||||||
results = task.result()
|
config.skip_categories = skip_categories
|
||||||
if len(results) == 0:
|
|
||||||
print("No channels found")
|
channel_whitelist = config.channel_whitelist
|
||||||
continue
|
choice = get_yn_input(WHITELIST_CHANNELS_PROMPT)
|
||||||
|
if choice == "y":
|
||||||
for i, item in enumerate(results):
|
if not apikey:
|
||||||
print(f"{i}: {item[1]} - Subs: {item[2]}")
|
print(
|
||||||
print("5: Enter a custom channel ID")
|
"WARNING: You need to specify an API key to use this function,"
|
||||||
print("6: Go back")
|
" otherwise the program will fail to start.\nYou can add one by"
|
||||||
|
" re-running this setup wizard."
|
||||||
choice = -1
|
)
|
||||||
choice = input("Select one option of the above [0-6]: ")
|
api_helper = api_helpers.ApiHelper(config, web_session)
|
||||||
while choice not in [str(x) for x in range(7)]:
|
while True:
|
||||||
print("Invalid choice")
|
channel_info = {}
|
||||||
choice = input("Select one option of the above [0-6]: ")
|
channel = input(SEARCH_CHANNEL_PROMPT)
|
||||||
|
if channel == "/exit":
|
||||||
if choice == "5":
|
break
|
||||||
channel_info["id"] = input("Enter a channel ID: ")
|
|
||||||
channel_info["name"] = input("Enter the channel name: ")
|
task = loop.create_task(
|
||||||
channel_whitelist.append(channel_info)
|
api_helper.search_channels(channel, apikey, web_session)
|
||||||
continue
|
)
|
||||||
if choice == "6":
|
loop.run_until_complete(task)
|
||||||
continue
|
results = task.result()
|
||||||
|
if len(results) == 0:
|
||||||
channel_info["id"] = results[int(choice)][0]
|
print("No channels found")
|
||||||
channel_info["name"] = results[int(choice)][1]
|
continue
|
||||||
channel_whitelist.append(channel_info)
|
|
||||||
# Close web session asynchronously
|
for i, item in enumerate(results):
|
||||||
|
print(f"{i}: {item[1]} - Subs: {item[2]}")
|
||||||
config.channel_whitelist = channel_whitelist
|
print("5: Enter a custom channel ID")
|
||||||
|
print("6: Go back")
|
||||||
config.skip_count_tracking = (
|
|
||||||
not input(
|
while choice := input(SELECT_CHANNEL_PROMPT):
|
||||||
"Do you want to report skipped segments to sponsorblock. Only the segment"
|
if choice in [str(x) for x in range(7)]:
|
||||||
" UUID will be sent? (y/n) "
|
break
|
||||||
)
|
print("Invalid choice")
|
||||||
== "n"
|
|
||||||
)
|
if choice == "5":
|
||||||
print("Config finished")
|
channel_info["id"] = input(ENTER_CHANNEL_ID_PROMPT)
|
||||||
config.save()
|
channel_info["name"] = input(ENTER_CUSTOM_CHANNEL_NAME_PROMPT)
|
||||||
loop.run_until_complete(web_session.close())
|
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())
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import argparse
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import rich_click as click
|
||||||
from appdirs import user_data_dir
|
from appdirs import user_data_dir
|
||||||
|
|
||||||
from . import config_setup, main, setup_wizard
|
from . import config_setup, main, setup_wizard
|
||||||
from .constants import config_file_blacklist_keys
|
from .constants import config_file_blacklist_keys
|
||||||
|
from .service.service_helpers import service
|
||||||
|
|
||||||
|
|
||||||
class Device:
|
class Device:
|
||||||
@@ -41,6 +42,7 @@ class Config:
|
|||||||
self.skip_count_tracking = True
|
self.skip_count_tracking = True
|
||||||
self.mute_ads = False
|
self.mute_ads = False
|
||||||
self.skip_ads = False
|
self.skip_ads = False
|
||||||
|
self.auto_play = True
|
||||||
self.__load()
|
self.__load()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -125,35 +127,95 @@ class Config:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def app_start():
|
@click.group(invoke_without_command=True)
|
||||||
# If env has a data dir use that, otherwise use the default
|
@click.option(
|
||||||
default_data_dir = os.getenv("iSPBTV_data_dir") or user_data_dir(
|
"--data",
|
||||||
"iSponsorBlockTV", "dmunozv04"
|
"-d",
|
||||||
)
|
default=lambda: os.getenv("iSPBTV_data_dir")
|
||||||
parser = argparse.ArgumentParser(description="iSponsorblockTV")
|
or user_data_dir("iSponsorBlockTV", "dmunozv04"),
|
||||||
parser.add_argument(
|
help="data directory",
|
||||||
"--data-dir", "-d", default=default_data_dir, help="data directory"
|
)
|
||||||
)
|
@click.option("--debug", is_flag=True, help="debug mode")
|
||||||
parser.add_argument(
|
# legacy commands as arguments
|
||||||
"--setup", "-s", action="store_true", help="setup the program graphically"
|
@click.option(
|
||||||
)
|
"--setup", is_flag=True, help="Setup the program graphically", hidden=True
|
||||||
parser.add_argument(
|
)
|
||||||
"--setup-cli",
|
@click.option(
|
||||||
"-sc",
|
"--setup-cli",
|
||||||
action="store_true",
|
is_flag=True,
|
||||||
help="setup the program in the command line",
|
help="Setup the program in the command line",
|
||||||
)
|
hidden=True,
|
||||||
parser.add_argument("--debug", action="store_true", help="debug mode")
|
)
|
||||||
args = parser.parse_args()
|
@click.pass_context
|
||||||
|
def cli(ctx, data, debug, setup, setup_cli):
|
||||||
config = Config(args.data_dir)
|
"""iSponsorblockTV"""
|
||||||
if args.debug:
|
ctx.ensure_object(dict)
|
||||||
|
ctx.obj["data_dir"] = data
|
||||||
|
ctx.obj["debug"] = debug
|
||||||
|
if debug:
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
if args.setup: # Set up the config file graphically
|
if ctx.invoked_subcommand is None:
|
||||||
setup_wizard.main(config)
|
if setup:
|
||||||
sys.exit()
|
ctx.invoke(setup_command)
|
||||||
if args.setup_cli: # Set up the config file
|
elif setup_cli:
|
||||||
config_setup.main(config, args.debug)
|
ctx.invoke(setup_cli_command)
|
||||||
else:
|
else:
|
||||||
config.validate()
|
ctx.invoke(start)
|
||||||
main.main(config, args.debug)
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def setup(ctx):
|
||||||
|
"""Setup the program graphically"""
|
||||||
|
config = Config(ctx.obj["data_dir"])
|
||||||
|
setup_wizard.main(config)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
setup_command = setup
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def setup_cli(ctx):
|
||||||
|
"""Setup the program in the command line"""
|
||||||
|
config = Config(ctx.obj["data_dir"])
|
||||||
|
config_setup.main(config, ctx.obj["debug"])
|
||||||
|
|
||||||
|
|
||||||
|
setup_cli_command = setup_cli
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def start(ctx):
|
||||||
|
"""Start the main program"""
|
||||||
|
config = Config(ctx.obj["data_dir"])
|
||||||
|
config.validate()
|
||||||
|
main.main(config, ctx.obj["debug"])
|
||||||
|
|
||||||
|
|
||||||
|
# Create fake "self" group to show pyapp options in help menu
|
||||||
|
# Subcommands remove, restore, update
|
||||||
|
pyapp_group = click.RichGroup("self", help="pyapp options (update, remove, restore)")
|
||||||
|
pyapp_group.add_command(
|
||||||
|
click.RichCommand("update", help="Update the package to the latest version")
|
||||||
|
)
|
||||||
|
pyapp_group.add_command(
|
||||||
|
click.Command(
|
||||||
|
"remove", help="Remove the package, wiping the installation but not the data"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pyapp_group.add_command(
|
||||||
|
click.RichCommand(
|
||||||
|
"restore", help="Restore the package to its original state by reinstalling it"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if os.getenv("PYAPP"):
|
||||||
|
cli.add_command(pyapp_group)
|
||||||
|
|
||||||
|
cli.add_command(service)
|
||||||
|
|
||||||
|
|
||||||
|
def app_start():
|
||||||
|
cli(obj={})
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import os
|
|
||||||
import plistlib
|
|
||||||
|
|
||||||
from . import config_setup
|
|
||||||
|
|
||||||
"""Not updated to V2 yet, should still work. Here be dragons"""
|
|
||||||
default_plist = {
|
|
||||||
"Label": "com.dmunozv04iSponsorBlockTV",
|
|
||||||
"RunAtLoad": True,
|
|
||||||
"StartInterval": 20,
|
|
||||||
"EnvironmentVariables": {"PYTHONUNBUFFERED": "YES"},
|
|
||||||
"StandardErrorPath": "", # Fill later
|
|
||||||
"StandardOutPath": "",
|
|
||||||
"ProgramArguments": "",
|
|
||||||
"WorkingDirectory": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def create_plist(path):
|
|
||||||
plist = default_plist
|
|
||||||
plist["ProgramArguments"] = [path + "/iSponsorBlockTV-macos"]
|
|
||||||
plist["StandardErrorPath"] = path + "/iSponsorBlockTV.error.log"
|
|
||||||
plist["StandardOutPath"] = path + "/iSponsorBlockTV.out.log"
|
|
||||||
plist["WorkingDirectory"] = path
|
|
||||||
launchd_path = os.path.expanduser("~/Library/LaunchAgents/")
|
|
||||||
path_to_save = launchd_path + "com.dmunozv04.iSponsorBlockTV.plist"
|
|
||||||
|
|
||||||
with open(path_to_save, "wb") as fp:
|
|
||||||
plistlib.dump(plist, fp)
|
|
||||||
|
|
||||||
|
|
||||||
def run_setup(file):
|
|
||||||
config = {}
|
|
||||||
config_setup.main(config, file, debug=False)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
correct_path = os.path.expanduser("~/iSponsorBlockTV")
|
|
||||||
if os.path.isfile(correct_path + "/iSponsorBlockTV-macos"):
|
|
||||||
print("Program is on the right path")
|
|
||||||
print("The launch daemon will now be installed")
|
|
||||||
create_plist(correct_path)
|
|
||||||
run_setup(correct_path + "/config.json")
|
|
||||||
print(
|
|
||||||
"Launch daemon installed. Please restart the computer to enable it or"
|
|
||||||
" use:\n launchctl load"
|
|
||||||
" ~/Library/LaunchAgents/com.dmunozv04.iSponsorBlockTV.plist"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if not os.path.exists(correct_path):
|
|
||||||
os.makedirs(correct_path)
|
|
||||||
print(
|
|
||||||
"Please move the program to the correct path: "
|
|
||||||
+ correct_path
|
|
||||||
+ "opening now on finder..."
|
|
||||||
)
|
|
||||||
os.system("open -R " + correct_path)
|
|
||||||
0
src/iSponsorBlockTV/service/__init__.py
Normal file
0
src/iSponsorBlockTV/service/__init__.py
Normal file
76
src/iSponsorBlockTV/service/service_helpers.py
Normal file
76
src/iSponsorBlockTV/service/service_helpers.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import rich_click as click
|
||||||
|
|
||||||
|
from .service_managers import select_service_manager
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.pass_context
|
||||||
|
def service(ctx):
|
||||||
|
"""Manage the program as a service (executable only)"""
|
||||||
|
ctx.ensure_object(dict)
|
||||||
|
if os.getenv("PYAPP") is None:
|
||||||
|
print(
|
||||||
|
"Service commands are only available in the executable version of the"
|
||||||
|
" program"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
ctx.obj["service_manager"] = select_service_manager()(os.getenv("PYAPP"))
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def start(ctx):
|
||||||
|
"""Start the service"""
|
||||||
|
ctx.obj["service_manager"].start()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def stop(ctx):
|
||||||
|
"""Stop the service"""
|
||||||
|
ctx.obj["service_manager"].stop()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def restart(ctx):
|
||||||
|
"""Restart the service"""
|
||||||
|
ctx.obj["service_manager"].restart()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def status(ctx):
|
||||||
|
"""Get the status of the service"""
|
||||||
|
ctx.obj["service_manager"].status()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def install(ctx):
|
||||||
|
"""Install the service"""
|
||||||
|
ctx.obj["service_manager"].install()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def uninstall(ctx):
|
||||||
|
"""Uninstall the service"""
|
||||||
|
ctx.obj["service_manager"].uninstall()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def enable(ctx):
|
||||||
|
"""Enable the service"""
|
||||||
|
ctx.obj["service_manager"].enable()
|
||||||
|
|
||||||
|
|
||||||
|
@service.command()
|
||||||
|
@click.pass_context
|
||||||
|
def disable(ctx):
|
||||||
|
"""Disable the service"""
|
||||||
|
ctx.obj["service_manager"].disable()
|
||||||
107
src/iSponsorBlockTV/service/service_managers.py
Normal file
107
src/iSponsorBlockTV/service/service_managers.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import os
|
||||||
|
import plistlib
|
||||||
|
import subprocess
|
||||||
|
from platform import system
|
||||||
|
|
||||||
|
from appdirs import user_log_dir
|
||||||
|
|
||||||
|
|
||||||
|
def select_service_manager() -> "ServiceManager":
|
||||||
|
platform = system()
|
||||||
|
if platform == "Darwin":
|
||||||
|
return Launchd
|
||||||
|
elif platform == "Linux":
|
||||||
|
return Systemd
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Unsupported platform")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceManager:
|
||||||
|
def __init__(self, executable_path, *args, **kwargs):
|
||||||
|
self.executable_path = executable_path
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def uninstall(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Launchd(ServiceManager):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.service_name = "com.dmunozv04.iSponsorBlockTV"
|
||||||
|
self.service_path = (
|
||||||
|
os.path.expanduser("~/Library/LaunchAgents/") + self.service_name + ".plist"
|
||||||
|
)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
subprocess.run(["launchctl", "start", self.service_name])
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
subprocess.run(["launchctl", "stop", self.service_name])
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
subprocess.run(["launchctl", "restart", self.service_name])
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
subprocess.run(["launchctl", "list", self.service_name])
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
if os.path.exists(self.service_path):
|
||||||
|
print("Service already installed")
|
||||||
|
return
|
||||||
|
logs_dir = user_log_dir("iSponsorBlockTV", "dmunozv04")
|
||||||
|
# ensure the logs directory exists
|
||||||
|
os.makedirs(logs_dir, exist_ok=True)
|
||||||
|
plist = {
|
||||||
|
"Label": "com.dmunozv04.iSponsorBlockTV",
|
||||||
|
"RunAtLoad": True,
|
||||||
|
"StartInterval": 20,
|
||||||
|
"EnvironmentVariables": {"PYTHONUNBUFFERED": "YES"},
|
||||||
|
"StandardErrorPath": logs_dir + "/iSponsorBlockTV.err",
|
||||||
|
"StandardOutPath": logs_dir + "/iSponsorBlockTV.out",
|
||||||
|
"Program": self.executable_path,
|
||||||
|
}
|
||||||
|
with open(self.service_path, "wb") as fp:
|
||||||
|
plistlib.dump(plist, fp)
|
||||||
|
print("Service installed")
|
||||||
|
self.enable()
|
||||||
|
|
||||||
|
def uninstall(self):
|
||||||
|
self.disable()
|
||||||
|
# Remove the file
|
||||||
|
try:
|
||||||
|
os.remove(self.service_path)
|
||||||
|
print("Service uninstalled")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Service not found")
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
subprocess.run(["launchctl", "load", self.service_path])
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
subprocess.run(["launchctl", "stop", self.service_name])
|
||||||
|
subprocess.run(["launchctl", "unload", self.service_path])
|
||||||
|
|
||||||
|
|
||||||
|
class Systemd(ServiceManager):
|
||||||
|
pass
|
||||||
@@ -363,3 +363,9 @@ MigrationScreen {
|
|||||||
width: 1fr;
|
width: 1fr;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Autoplay */
|
||||||
|
#autoplay-container{
|
||||||
|
padding: 1;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -850,6 +850,32 @@ class ChannelWhitelistManager(Vertical):
|
|||||||
self.app.push_screen(AddChannel(self.config), callback=self.new_channel)
|
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):
|
class ISponsorBlockTVSetupMainScreen(Screen):
|
||||||
"""Making this a separate screen to avoid a bug: https://github.com/Textualize/textual/issues/3221"""
|
"""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(
|
yield ApiKeyManager(
|
||||||
config=self.config, id="api-key-manager", classes="container"
|
config=self.config, id="api-key-manager", classes="container"
|
||||||
)
|
)
|
||||||
|
yield AutoPlayManager(
|
||||||
|
config=self.config, id="autoplay-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():
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
|
|||||||
self.callback = None
|
self.callback = None
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.shorts_disconnected = False
|
self.shorts_disconnected = False
|
||||||
|
self.auto_play = True
|
||||||
if config:
|
if config:
|
||||||
self.mute_ads = config.mute_ads
|
self.mute_ads = config.mute_ads
|
||||||
self.skip_ads = config.skip_ads
|
self.skip_ads = config.skip_ads
|
||||||
|
self.auto_play = config.auto_play
|
||||||
|
|
||||||
# Ensures that we still are subscribed to the lounge
|
# Ensures that we still are subscribed to the lounge
|
||||||
async def _watchdog(self):
|
async def _watchdog(self):
|
||||||
@@ -146,6 +148,8 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
|
|||||||
data = args[0]
|
data = args[0]
|
||||||
if data["reason"] == "disconnectedByUserScreenInitiated": # Short playing?
|
if data["reason"] == "disconnectedByUserScreenInitiated": # Short playing?
|
||||||
self.shorts_disconnected = True
|
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)
|
super()._process_event(event_id, event_type, args)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user