diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3acd1c..ba55c58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,53 +7,60 @@ env: jobs: build: - #if: ${{ github.event.workflow_run.conclusion == 'success' }} strategy: fail-fast: false matrix: - os: [windows-latest] - python-version: [3.11] #3.7, 3.8 + os: [ubuntu-20.04] + python-version: [3.11] runs-on: ${{ matrix.os }} - name: Build PyInstaller executable ${{ matrix.os }} + Py ${{ matrix.python-version }} + name: Build ${{ matrix.os }} executable steps: - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} -# cache: 'poetry' + - name: Install poetry + run: pip install poetry - name: Get packages via poetry run: poetry install -# chrome is not packaged within site-packages, chromium could be used as fallback in the future, but size ~100MB -# - name: Install playwright -# run: | -# poetry run python -m playwright install chromium -# poetry run python -m playwright install chrome -# ls - - - name: Create date env variable - shell: bash - run: | - echo "today=$(date +'%Y%m%d')" >> $GITHUB_ENV - - - name: Get CTVB version number + - name: Get CTVBot version number uses: SebRollen/toml-action@v1.0.0 id: read_toml with: file: 'pyproject.toml' field: 'tool.poetry.version' + - name: Create date and file name env variable + shell: bash + run: | + export today=$(date +'%Y%m%d') + if [ "$RUNNER_OS" == "Windows" ]; then + echo "output_filename=CTVBot${{steps.read_toml.outputs.value}}_${today}_${{runner.os}}-${{runner.arch}}" >> $GITHUB_ENV + else + echo "output_filename=CTVBot${{steps.read_toml.outputs.value}}_${today}_${{runner.os}}-${{runner.arch}}_experimental" >> $GITHUB_ENV + fi + - name: Build executable + shell: bash run: | - poetry run pyinstaller main_gui.py --onefile --add-binary "ctvbot_logo.ico;." --add-binary "pyproject.toml;." --icon ctvbot_logo.ico --name CTVB_${{steps.read_toml.outputs.value}}_${{env.today}} + if [ "$RUNNER_OS" == "Windows" ]; then + poetry run pyinstaller main_gui.py --onefile --add-binary "ctvbot_logo.ico;." --add-binary "pyproject.toml;." --icon ctvbot_logo.ico --name ${{env.output_filename}} + else + poetry run pyinstaller main_gui.py --onefile --add-binary "ctvbot_logo.ico:." --hidden-import=tkinter --add-binary "pyproject.toml:." --icon ctvbot_logo.ico --name ${{env.output_filename}}.bin + fi mv ./proxy/ ./dist/ - - name: Upload artifact ${{ matrix.os }} + - name: Zip executable + uses: vimtor/action-zip@v1 + with: + files: dist/ + dest: ${{env.output_filename}}.zip + + - name: Upload zipped artifact ${{ matrix.os }} uses: actions/upload-artifact@v1 with: - name: CTVB_${{ steps.read_toml.outputs.value }}_${{ env.today }} - path: dist/ + name: ${{env.output_filename}} + path: ${{env.output_filename}}.zip diff --git a/.github/workflows/build_release_draft.yml b/.github/workflows/build_release_draft.yml index 2972734..298c105 100644 --- a/.github/workflows/build_release_draft.yml +++ b/.github/workflows/build_release_draft.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2022, ubuntu-22.04, macos-12, macos-14] #14 is M1 + os: [windows-2022, ubuntu-20.04, macos-12, macos-14] #14 is M1 python-version: [3.11] runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 97bbb4a..696d616 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,17 @@ Get exlusive Feature Previews as a [:gem: Supporter & Feature Tester](https://ko ### Platform Support Overview -| Platform | Twitch | Youtube | Kick | -|-----------------------|:-----------------------------------------:|:------------------:|:---------:| -| General Functionality | :heavy_check_mark: | :heavy_check_mark: | :warning: | -| Lowest Quality Select | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Status Boxes Updates | :heavy_check_mark: | :heavy_check_mark: | :x: | -| Login/Authentication | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/235) | :x: | :x: | -| Automatic Follow | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/235) | :x: | :x: | -| Automatic Chat | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/235) | :x: | :x: | -| Low CPU Usage Mode | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/235) | :x: | :x: | - -:heavy_check_mark: Supported, :warning: Problems, :x: Unsupported, ⏳ In Development, [:gem: Preview Available](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/235) +| Platform | Twitch | Youtube | Chzzk | Kick | +|-----------------------|:-----------------------------------------------------------------------------:|:------------------:|:------------------:|:-----------------------------------------------------------------------------:| +| General Functionality | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | +| Lowest Quality Select | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | +| Status Boxes Updates | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | +| Login/Authentication | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | :x: | :x: | :x: | +| Automatic Follow | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | :x: | :x: | :x: | +| Automatic Chat | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | :x: | :x: | :x: | +| Low CPU Usage Mode | ⏳[:gem:](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) | :x: | :x: | :x: | + +:heavy_check_mark: Supported, :warning: Problems, :x: Unsupported, ⏳ In Development, [:gem: Preview Available](https://github.com/jlplenio/crude-twitch-viewer-bot/discussions/256) ### In Action diff --git a/ctvbot/instance.py b/ctvbot/instance.py index 7cfbfe4..cc66013 100644 --- a/ctvbot/instance.py +++ b/ctvbot/instance.py @@ -12,8 +12,10 @@ class Instance(ABC): - name = "BASE" + site_name = "BASE" + site_url = None instance_lock = threading.Lock() + supported_sites = dict() def __init__( self, @@ -57,6 +59,10 @@ def __init__( self.command = None self.page = None + def __init_subclass__(cls, **kwargs): + if cls.site_name != "UNKNOWN": + cls.supported_sites[cls.site_url] = cls + @property def status(self): return self._status @@ -84,7 +90,7 @@ def start(self): except Exception as e: message = e.args[0][:25] if e.args else "" logger.exception(f"{e} died at page {self.page.url if self.page else None}") - print(f"{self.name} Instance {self.id} died: {type(e).__name__}:{message}... Please see ctvbot.log.") + print(f"{self.site_name} Instance {self.id} died: {type(e).__name__}:{message}... Please see ctvbot.log.") else: logger.info(f"ENDED: instance {self.id}") with self.instance_lock: diff --git a/ctvbot/manager.py b/ctvbot/manager.py index f66f4e2..3b7c6b2 100644 --- a/ctvbot/manager.py +++ b/ctvbot/manager.py @@ -7,6 +7,7 @@ import time from . import logger_config, utils, sites +from .instance import Instance logger_config.setup() from .proxy import ProxyGetter @@ -110,7 +111,7 @@ def spawn_instances(self, n, target_url=None): time.sleep(self.spawn_interval_seconds) def get_site_class(self, target_url): - for site_name, site_class in utils.supported_sites.items(): + for site_name, site_class in Instance.supported_sites.items(): if site_name in target_url: return site_class @@ -167,7 +168,7 @@ def spawn_instance_thread(self, target_url, status_reporter, browser_instance_id server_ip = proxy.get("server", "no proxy") logger.info( - f"Ordered {site_class.name} instance {browser_instance_id}, {threading.currentThread().name}, proxy {server_ip}, user_agent {user_agent}" + f"Ordered {site_class.site_name} instance {browser_instance_id}, {threading.currentThread().name}, proxy {server_ip}, user_agent {user_agent}" ) browser_instance = site_class( diff --git a/ctvbot/sites.py b/ctvbot/sites.py index 922f8d2..878e873 100644 --- a/ctvbot/sites.py +++ b/ctvbot/sites.py @@ -12,7 +12,8 @@ class Unknown(Instance): - name = "UNKNOWN" + site_name = "UNKNOWN" + site_url = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -28,14 +29,70 @@ def todo_after_spawn(self): self.page.wait_for_timeout(1000) +class Chzzk(Instance): + site_name = "CHZZK" + site_url = "chzzk.naver.com" + + local_storage = {"live-player-video-track": r"""{"label":"360p","kind":"low-latency","width":640,"height":360}"""} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.status_info = {} + + def todo_every_loop(self): + try: + self.page.click("button.btn_skip", timeout=100) + except: + pass + + def update_status(self): + html = self.page.evaluate('document.querySelector("div#live_player_layout").innerHTML') + if "pzp-pc--live" in html: + if "pzp-pc--loading" not in html: + self.status = utils.InstanceStatus.WATCHING + return + self.status = utils.InstanceStatus.BUFFERING + + def todo_after_spawn(self): + self.goto_with_retry("https://chzzk.naver.com/category") + + self.page.wait_for_timeout(1000) + + for key, value in self.local_storage.items(): + tosend = """window.localStorage.setItem('{key}','{value}');""".format(key=key, value=value) + self.page.evaluate(tosend) + + self.goto_with_retry(self.target_url) + + self.page.wait_for_selector("#live_player_layout", timeout=30000) + self.page.wait_for_timeout(1000) + self.page.keyboard.press("f") + + self.page.set_viewport_size( + { + "width": self.location_info["width"], + "height": self.location_info["height"], + } + ) + + self.status = utils.InstanceStatus.INITIALIZED + + def todo_after_load(self): + self.page.wait_for_selector("#live_player_layout", timeout=30000) + self.page.wait_for_timeout(1000) + self.page.keyboard.press("f") + + class Youtube(Instance): - name = "YOUTUBE" + site_name = "YOUTUBE" + site_url = "youtube.com" cookie_css = ".eom-button-row.style-scope.ytd-consent-bump-v2-lightbox > ytd-button-renderer:nth-child(1) button" now_timestamp_ms = int(time.time() * 1000) next_year_timestamp_ms = int((datetime.datetime.now() + relativedelta(years=1)).timestamp() * 1000) local_storage = { - "yt-player-quality": r"""{{"data":"{{\\"quality\\":144,\\"previousQuality\\":144}}","expiration":{0},"creation":{1}}}""".format( + "yt-player-quality": r"""{{"data":"{{\\"quality\\":144,\\"previousQuality\\":144}}","expiration":{0}, + "creation":{1}}}""".format( next_year_timestamp_ms, now_timestamp_ms ), } @@ -108,7 +165,8 @@ def todo_after_spawn(self): class Kick(Instance): - name = "KICK" + site_name = "KICK" + site_url = "kick.com" local_storage = { "agreed_to_mature_content": "true", "kick_cookie_accepted": "true", @@ -139,7 +197,8 @@ def todo_after_spawn(self): class Twitch(Instance): - name = "TWITCH" + site_name = "TWITCH" + site_url = "twitch.tv" cookie_css = "button[data-a-target=consent-banner-accept]" local_storage = { "mature": "true", diff --git a/ctvbot/utils.py b/ctvbot/utils.py index 60f58c3..d23e7b6 100644 --- a/ctvbot/utils.py +++ b/ctvbot/utils.py @@ -1,12 +1,6 @@ from enum import Enum, auto from ctvbot import sites -supported_sites = { - "twitch.tv/": sites.Twitch, - "youtube.com/": sites.Youtube, - "kick.com/": sites.Kick, -} - class InstanceCommands(Enum): SCREENSHOT = auto()