From ff22f47b90fe84ed31b44adbaddb9878f35e1fdb Mon Sep 17 00:00:00 2001 From: 0815Cracky <0815Cracky@gmail.com> Date: Tue, 27 Feb 2024 11:46:37 +0100 Subject: [PATCH] update --- .github/ISSUE_TEMPLATE/bug_report.yml | 79 ++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 31 + .github/PULL_REQUEST_TEMPLATE.md | 26 + .github/dependabot.yml | 6 + .github/stale.yml | 24 + .github/workflows/deploy-docker.yml | 61 ++ .../workflows/github-clone-count-badge.yml | 90 ++ .../workflows/github-traffic-count-badge.yml | 83 ++ .gitignore | 157 ++++ .pre-commit-config.yaml | 26 + .vscode/launch.json | 16 + CLONE.md | 13 + CODE_OF_CONDUCT.md | 76 ++ CONTRIBUTING.md | 110 +++ DELETE_PYCACHE.bat | 5 + Dockerfile | 43 + README.md | 743 +++++++++++++++ TRAFFIC.md | 11 + .../TwitchChannelPointsMiner.py | 496 ++++++++++ TwitchChannelPointsMiner/__init__.py | 7 + .../classes/AnalyticsServer.py | 295 ++++++ TwitchChannelPointsMiner/classes/Chat.py | 105 +++ TwitchChannelPointsMiner/classes/Discord.py | 24 + .../classes/Exceptions.py | 14 + TwitchChannelPointsMiner/classes/Matrix.py | 40 + TwitchChannelPointsMiner/classes/Pushover.py | 30 + TwitchChannelPointsMiner/classes/Settings.py | 53 ++ TwitchChannelPointsMiner/classes/Telegram.py | 29 + TwitchChannelPointsMiner/classes/Twitch.py | 859 ++++++++++++++++++ .../classes/TwitchLogin.py | 360 ++++++++ .../classes/TwitchWebSocket.py | 65 ++ .../classes/WebSocketsPool.py | 434 +++++++++ TwitchChannelPointsMiner/classes/Webhook.py | 26 + TwitchChannelPointsMiner/classes/__init__.py | 0 .../classes/entities/Bet.py | 315 +++++++ .../classes/entities/Campaign.py | 74 ++ .../classes/entities/Drop.py | 103 +++ .../classes/entities/EventPrediction.py | 94 ++ .../classes/entities/Message.py | 69 ++ .../classes/entities/PubsubTopic.py | 16 + .../classes/entities/Raid.py | 12 + .../classes/entities/Stream.py | 107 +++ .../classes/entities/Streamer.py | 284 ++++++ .../classes/entities/__init__.py | 0 TwitchChannelPointsMiner/constants.py | 199 ++++ TwitchChannelPointsMiner/logger.py | 342 +++++++ TwitchChannelPointsMiner/utils.py | 212 +++++ assets/banner.png | Bin 0 -> 105907 bytes assets/chart-analytics-dark.png | Bin 0 -> 130178 bytes assets/chart-analytics-light.png | Bin 0 -> 125507 bytes assets/charts.html | 209 +++++ assets/dark-theme.css | 39 + assets/prediction.png | Bin 0 -> 30993 bytes assets/script.js | 389 ++++++++ assets/style.css | 70 ++ example.py | 128 +++ pickle_view.py | 13 + requirements.txt | 12 + setup.py | 58 ++ 60 files changed, 7183 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/stale.yml create mode 100644 .github/workflows/deploy-docker.yml create mode 100644 .github/workflows/github-clone-count-badge.yml create mode 100644 .github/workflows/github-traffic-count-badge.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/launch.json create mode 100644 CLONE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 DELETE_PYCACHE.bat create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 TRAFFIC.md create mode 100644 TwitchChannelPointsMiner/TwitchChannelPointsMiner.py create mode 100644 TwitchChannelPointsMiner/__init__.py create mode 100644 TwitchChannelPointsMiner/classes/AnalyticsServer.py create mode 100644 TwitchChannelPointsMiner/classes/Chat.py create mode 100644 TwitchChannelPointsMiner/classes/Discord.py create mode 100644 TwitchChannelPointsMiner/classes/Exceptions.py create mode 100644 TwitchChannelPointsMiner/classes/Matrix.py create mode 100644 TwitchChannelPointsMiner/classes/Pushover.py create mode 100644 TwitchChannelPointsMiner/classes/Settings.py create mode 100644 TwitchChannelPointsMiner/classes/Telegram.py create mode 100644 TwitchChannelPointsMiner/classes/Twitch.py create mode 100644 TwitchChannelPointsMiner/classes/TwitchLogin.py create mode 100644 TwitchChannelPointsMiner/classes/TwitchWebSocket.py create mode 100644 TwitchChannelPointsMiner/classes/WebSocketsPool.py create mode 100644 TwitchChannelPointsMiner/classes/Webhook.py create mode 100644 TwitchChannelPointsMiner/classes/__init__.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Bet.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Campaign.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Drop.py create mode 100644 TwitchChannelPointsMiner/classes/entities/EventPrediction.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Message.py create mode 100644 TwitchChannelPointsMiner/classes/entities/PubsubTopic.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Raid.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Stream.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Streamer.py create mode 100644 TwitchChannelPointsMiner/classes/entities/__init__.py create mode 100644 TwitchChannelPointsMiner/constants.py create mode 100644 TwitchChannelPointsMiner/logger.py create mode 100644 TwitchChannelPointsMiner/utils.py create mode 100644 assets/banner.png create mode 100644 assets/chart-analytics-dark.png create mode 100644 assets/chart-analytics-light.png create mode 100644 assets/charts.html create mode 100644 assets/dark-theme.css create mode 100644 assets/prediction.png create mode 100644 assets/script.js create mode 100644 assets/style.css create mode 100644 example.py create mode 100644 pickle_view.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..72244af --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,79 @@ +name: Bug report +description: Create a report to help us improve +labels: bug + +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: | + A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: What do you expect to happen? + validations: + required: true + - type: input + id: operating-system + attributes: + label: Operating system + placeholder: Windows 11 Version 21H2 (OS Build 22000.1574) + validations: + required: true + - type: input + id: python-version + attributes: + label: Python version + placeholder: "3.11.1" + validations: + required: true + - type: input + id: miner-version + attributes: + label: Miner version + placeholder: "1.7.7" + validations: + required: true + - type: textarea + id: other-environment-info + attributes: + label: Other relevant software versions + - type: textarea + id: logs + attributes: + label: Logs + description: | + How to provide a DEBUG log: + 1. Set this in your runner script (`run.py`): + ```py + logger_settings=LoggerSettings( + save=True, + console_level=logging.INFO, + file_level=logging.DEBUG, + less=True, + ``` + 2. Start the miner, wait for the error, then stop the miner and post the contents of the log file (`logs\username.log`) to https://gist.github.com/ and post a link here. + 3. Create another gist with your console output, just in case. Paste a link here as well. + validations: + required: true + - type: textarea + id: other-info + attributes: + label: Additional context + description: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..d4f2a80 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,31 @@ +name: Feature request +description: Suggest an idea for this project +labels: enhancement + +body: + - type: textarea + id: description + attributes: + label: Is your feature request related to a problem? + description: A clear and concise description of what the problem is. + placeholder: I'm always frustrated when [...] + - type: textarea + id: solution + attributes: + label: Proposed solution + description: | + Suggest your feature here. What benefit would it bring? + + Do you have any ideas on how to implement it? + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives you've considered + description: Suggest any alternative solutions or features you've considered. + - type: textarea + id: other-info + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4aef640 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented on my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation (README.md) +- [ ] My changes generate no new warnings +- [ ] Any dependent changes have been updated in requirements.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1230149 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..073f80c --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,24 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 150 + +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 + +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - bug + - enhancement + +# Label to use when marking an issue as stale +staleLabel: wontfix + +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml new file mode 100644 index 0000000..751b40d --- /dev/null +++ b/.github/workflows/deploy-docker.yml @@ -0,0 +1,61 @@ +name: deploy-docker + +on: + push: + # branches: [master] + tags: + - '*' + workflow_dispatch: + +jobs: + deploy-docker: + name: Deploy Docker Hub + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Login to DockerHub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: rdavidoff/twitch-channel-points-miner-v2 + tags: | + type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags') }} + type=raw,value=latest + + - name: Build and push AMD64, ARM64, ARMv7 + id: docker_build + uses: docker/build-push-action@v5.1.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64,linux/arm/v7 + build-args: BUILDX_QEMU_ENV=true + cache-from: type=gha + cache-to: type=gha,mode=max + + # File size exceeds the maximum allowed 25000 bytes + # - name: Docker Hub Description + # uses: peter-evans/dockerhub-description@v2 + # with: + # username: ${{ secrets.DOCKER_USERNAME }} + # password: ${{ secrets.DOCKER_TOKEN }} + # repository: rdavidoff/twitch-channel-points-miner-v2 + + - name: Image digest AMD64, ARM64, ARMv7 + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/github-clone-count-badge.yml b/.github/workflows/github-clone-count-badge.yml new file mode 100644 index 0000000..00a7e88 --- /dev/null +++ b/.github/workflows/github-clone-count-badge.yml @@ -0,0 +1,90 @@ +name: GitHub Clone Count Update Everyday + +on: + schedule: + - cron: "0 */24 * * *" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: gh login + run: echo "${{ secrets.SECRET_TOKEN }}" | gh auth login --with-token + + - name: parse latest clone count + run: | + curl --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/traffic/clones \ + > clone.json + + - name: create gist and download previous count + id: set_id + run: | + if gh secret list | grep -q "GIST_ID" + then + echo "GIST_ID found" + echo ::set-output name=GIST::${{ secrets.GIST_ID }} + curl https://gist.githubusercontent.com/${{ github.actor }}/${{ secrets.GIST_ID }}/raw/clone.json > clone_before.json + if cat clone_before.json | grep '404: Not Found'; then + echo "GIST_ID not valid anymore. Creating another gist..." + gist_id=$(gh gist create clone.json | awk -F / '{print $NF}') + echo $gist_id | gh secret set GIST_ID + echo ::set-output name=GIST::$gist_id + cp clone.json clone_before.json + git rm --ignore-unmatch CLONE.md + fi + else + echo "GIST_ID not found. Creating a gist..." + gist_id=$(gh gist create clone.json | awk -F / '{print $NF}') + echo $gist_id | gh secret set GIST_ID + echo ::set-output name=GIST::$gist_id + cp clone.json clone_before.json + fi + + - name: update clone.json + run: | + curl https://raw.githubusercontent.com/MShawon/github-clone-count-badge/master/main.py > main.py + python3 main.py + + - name: Update gist with latest count + run: | + content=$(sed -e 's/\\/\\\\/g' -e 's/\t/\\t/g' -e 's/\"/\\"/g' -e 's/\r//g' "clone.json" | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g') + echo '{"description": "${{ github.repository }} clone statistics", "files": {"clone.json": {"content": "'"$content"'"}}}' > post_clone.json + curl -s -X PATCH \ + --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @post_clone.json https://api.github.com/gists/${{ steps.set_id.outputs.GIST }} > /dev/null 2>&1 + + if [ ! -f CLONE.md ]; then + shields="https://img.shields.io/badge/dynamic/json?color=success&label=Clone&query=count&url=" + url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/clone.json" + repo="https://github.com/MShawon/github-clone-count-badge" + echo ''> CLONE.md + echo ' + **Markdown** + + ```markdown' >> CLONE.md + echo "[![GitHub Clones]($shields$url&logo=github)]($repo)" >> CLONE.md + echo ' + ``` + + **HTML** + ```html' >> CLONE.md + echo "GitHub Clones" >> CLONE.md + echo '```' >> CLONE.md + + git add CLONE.md + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git commit -m "create clone count badge" + fi + + - name: Push + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/github-traffic-count-badge.yml b/.github/workflows/github-traffic-count-badge.yml new file mode 100644 index 0000000..07b54cd --- /dev/null +++ b/.github/workflows/github-traffic-count-badge.yml @@ -0,0 +1,83 @@ +name: GitHub Traffic Count Update Everyday + +on: + schedule: + - cron: "0 */24 * * *" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: gh login + run: echo "${{ secrets.SECRET_TOKEN }}" | gh auth login --with-token + + - name: parse latest traffic count + run: | + curl --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/traffic/views \ + > traffic.json + - name: create gist and download previous count + id: set_id + run: | + if gh secret list | grep -q "TRAFFIC_ID" + then + echo "TRAFFIC_ID found" + echo ::set-output name=GIST::${{ secrets.TRAFFIC_ID }} + curl https://gist.githubusercontent.com/${{ github.actor }}/${{ secrets.TRAFFIC_ID }}/raw/traffic.json > traffic_before.json + if cat traffic_before.json | grep '404: Not Found'; then + echo "TRAFFIC_ID not valid anymore. Creating another gist..." + traffic_id=$(gh gist create traffic.json | awk -F / '{print $NF}') + echo $traffic_id | gh secret set TRAFFIC_ID + echo ::set-output name=GIST::$traffic_id + cp traffic.json traffic_before.json + git rm --ignore-unmatch TRAFFIC.md + fi + else + echo "TRAFFIC_ID not found. Creating a gist..." + traffic_id=$(gh gist create traffic.json | awk -F / '{print $NF}') + echo $traffic_id | gh secret set TRAFFIC_ID + echo ::set-output name=GIST::$traffic_id + cp traffic.json traffic_before.json + fi + - name: update traffic.json + run: | + curl https://gist.githubusercontent.com/MShawon/d37c49ee4ce03f64b92ab58b0cec289f/raw/traffic.py > traffic.py + python3 traffic.py + - name: Update gist with latest count + run: | + content=$(sed -e 's/\\/\\\\/g' -e 's/\t/\\t/g' -e 's/\"/\\"/g' -e 's/\r//g' "traffic.json" | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g') + echo '{"description": "${{ github.repository }} traffic statistics", "files": {"traffic.json": {"content": "'"$content"'"}}}' > post_traffic.json + curl -s -X PATCH \ + --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @post_traffic.json https://api.github.com/gists/${{ steps.set_id.outputs.GIST }} > /dev/null 2>&1 + if [ ! -f TRAFFIC.md ]; then + shields="https://img.shields.io/badge/dynamic/json?color=success&label=Views&query=count&url=" + url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/traffic.json" + repo="https://github.com/MShawon/github-clone-count-badge" + echo ''> TRAFFIC.md + echo ' + **Markdown** + ```markdown' >> TRAFFIC.md + echo "[![GitHub Traffic]($shields$url&logo=github)]($repo)" >> TRAFFIC.md + echo ' + ``` + **HTML** + ```html' >> TRAFFIC.md + echo "GitHub Traffic" >> TRAFFIC.md + echo '```' >> TRAFFIC.md + + git add TRAFFIC.md + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git commit -m "create traffic count badge" + fi + - name: Push + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ed07d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,157 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# Custom files +run.py +chromedriver* + +# Folders +cookies/* +logs/* +screenshots/* +htmls/* +analytics/* + +# Replit +keep_replit_alive.py +.replit +replit.nix diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5a07a6c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + files: ^TwitchChannelPointsMiner/ + args: ["--profile", "black"] +- repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + files: ^TwitchChannelPointsMiner/ +- repo: https://github.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + files: ^TwitchChannelPointsMiner/ + args: + - "--max-line-length=88" + - "--extend-ignore=E501" diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..be31cde --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: run.py", + "type": "python", + "request": "launch", + "program": "${cwd}/run.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/CLONE.md b/CLONE.md new file mode 100644 index 0000000..48f7e98 --- /dev/null +++ b/CLONE.md @@ -0,0 +1,13 @@ + + + **Markdown** + + ```markdown +[![GitHub Clones](https://img.shields.io/badge/dynamic/json?color=success&label=Clone&query=count&url=https://gist.githubusercontent.com/rdavydov/fed04b31a250ad522d9ea6547ce87f95/raw/clone.json&logo=github)](https://github.com/MShawon/github-clone-count-badge) + + ``` + + **HTML** + ```html +GitHub Clones +``` diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2b46b5b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at alex.tkd.alex@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..561e067 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing to this repository + +## Getting started + +Before you begin: +- Have you read the [code of conduct](CODE_OF_CONDUCT.md)? +- Check out the [existing issues](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues) & see if there is already an opened issue. + +### Ready to make a change? Fork the repo + +Fork using GitHub Desktop: + +- [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop. +- Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)! + +Fork using the command line: + +- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. + +Fork with [GitHub Codespaces](https://github.com/features/codespaces): + +- [Fork, edit, and preview](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace) using [GitHub Codespaces](https://github.com/features/codespaces) without having to install and run the project locally. + +### Open a pull request +When you're done making changes, and you'd like to propose them for review, use the [pull request template](#pull-request-template) to open your PR (pull request). + +### Submit your PR & get it reviewed +- Once you submit your PR, other users from the community will review it with you. The first thing you're going to want to do is a [self review](#self-review). +- After that, we may have questions. Check back on your PR to keep up with the conversation. +- Did you have an issue, like a merge conflict? Check out our [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) on resolving merge conflicts and other issues. + +### Your PR is merged! +Congratulations! The whole GitHub community thanks you. :sparkles: + +Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/graphs/contributors). + +### Keep contributing as you use GitHub Docs + +Now that you're a part of the GitHub Docs community, you can keep participating in many ways. + +**Learn more about contributing:** + +- [Types of contributions :memo:](#types-of-contributions-memo) + - [:beetle: Issues](#beetle-issues) + - [:hammer_and_wrench: Pull requests](#hammer_and_wrench-pull-requests) +- [Starting with an issue](#starting-with-an-issue) + - [Labels](#labels) +- [Opening a pull request](#opening-a-pull-request) +- [Reviewing](#reviewing) + - [Self review](#self-review) + - [Pull request template](#pull-request-template) + - [Python Styleguide](#python-styleguide) + - [Suggested changes](#suggested-changes) + +## Types of contributions :memo: +You can contribute to the Twitch-Channel-Points-Miner-v2 in several ways. Bug reporting, pull request, propose new features, fork, donate, and much more :muscle: . + +### :beetle: Issues +[Issues](https://docs.github.com/en/github/managing-your-work-on-github/about-issues) are used to report a bug, propose new features, or ask for help. When you open an issue, please use the appropriate template and label. + +### :hammer_and_wrench: Pull requests +A [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) is a way to suggest changes in our repository. + +When we merge those changes, they should be deployed to the live site within 24 hours. :earth_africa: To learn more about opening a pull request in this repo, see [Opening a pull request](#opening-a-pull-request) below. + +## Starting with an issue +You can browse existing issues to find something that needs help! + +### Labels +Labels can help you find an issue you'd like to help with. +- The `bug` label is used when something isn't working +- The `documentation` label is used when you suggest improvements or additions to documentation (README.md update) +- The `duplicate` label is used when this issue or pull request already exists +- The `enhancement` label is used when you ask for / or propose a new feature or request +- The `help wanted` is used when you need help with something +- The `improvements` label is used when you would suggest improvements on already existing features +- The `invalid` label is used for a non-valid issue +- The `question` label is used for further information is requested +- The `wontfix` label is used if we will not work on it + +## Opening a pull request +You can use the GitHub user interface :pencil2: for minor changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally to view changes and run your tests on your machine. + +### Self review +You should always review your own PR first. + +For content changes, make sure that you: +- [ ] Confirm that the changes address every part of the content design plan from your issue (if there are differences, explain them). +- [ ] Review the content for technical accuracy. +- [ ] Review the entire pull request using the checklist present in the template. +- [ ] Copy-edit the changes for grammar, spelling, and adherence to the style guide. +- [ ] Check new or updated Liquid statements to confirm that versioning is correct. +- [ ] Check that all of your changes render correctly in staging. Remember, that lists and tables can be tricky. +- [ ] If there are any failing checks in your PR, troubleshoot them until they're all passing. + +### Pull request template +When you open a pull request, you must fill out the "Ready for review" template before we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request. + +### Python Styleguide +All Python code is formatted with [Black](https://github.com/psf/black) using the default settings. Your code will not be accepted if it is not blackened. +You can use the pre-commit hook. +``` +pip install pre-commit +pre-commit install +``` + +### Suggested changes +We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. + +As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). diff --git a/DELETE_PYCACHE.bat b/DELETE_PYCACHE.bat new file mode 100644 index 0000000..37901f4 --- /dev/null +++ b/DELETE_PYCACHE.bat @@ -0,0 +1,5 @@ +@echo off +rmdir /s /q __pycache__ +rmdir /s /q TwitchChannelPointsMiner\__pycache__ +rmdir /s /q TwitchChannelPointsMiner\classes\__pycache__ +rmdir /s /q TwitchChannelPointsMiner\classes\entities\__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0bec4ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM python:3.12 + +ARG BUILDX_QEMU_ENV + +WORKDIR /usr/src/app + +COPY ./requirements.txt ./ + +ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 + +RUN pip install --upgrade pip + +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ + gcc \ + libffi-dev \ + rustc \ + zlib1g-dev \ + libjpeg-dev \ + libssl-dev \ + libblas-dev \ + liblapack-dev \ + make \ + cmake \ + automake \ + ninja-build \ + g++ \ + subversion \ + python3-dev \ + && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ + pip install -U cryptography==3.3.2; \ + fi \ + && pip install -r requirements.txt \ + && pip cache purge \ + && apt-get remove -y gcc rustc \ + && apt-get autoremove -y \ + && apt-get autoclean -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /usr/share/doc/* + +ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner +ENTRYPOINT [ "python", "run.py" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ca8960 --- /dev/null +++ b/README.md @@ -0,0 +1,743 @@ +![Twitch Channel Points Miner - v2](https://raw.githubusercontent.com/rdavydov/Twitch-Channel-Points-Miner-v2/master/assets/banner.png) +

+Latest Version +GitHub Repo stars +GitHub Traffic +GitHub Clones +License +GitHub last commit +

+ +

+Docker Version +Docker Stars +Docker Pulls +Docker Images Size AMD64 +Docker Images Size ARM64 +Docker Images Size ARMv7 +

+ + +

https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2

+ +**Credits** +- Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner +- ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ +- Based on: https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2 + +> A simple script that will watch a stream for you and earn the channel points. + +> It can wait for a streamer to go live (+_450 points_ when the stream starts), it will automatically click the bonus button (_+50 points_), and it will follow raids (_+250 points_). + +Read more about the channel points [here](https://help.twitch.tv/s/article/channel-points-guide). + +# README Contents +1. 🀝 [Community](#community) +2. πŸš€ [Main differences from the original repository](#main-differences-from-the-original-repository) +3. 🧾 [Logs feature](#logs-feature) + - [Full logs](#full-logs) + - [Less logs](#less-logs) + - [Final report](#final-report) +4. 🧐 [How to use](#how-to-use) + - [Cloning](#by-cloning-the-repository) + - [Docker](#docker) + - [Docker Hub](#docker-hub) + - [Portainer](#portainer) + - [Replit](#replit) + - [Limits](#limits) +5. πŸ”§ [Settings](#settings) + - [LoggerSettings](#loggersettings) + - [StreamerSettings](#streamersettings) + - [BetSettings](#betsettings) + - [Bet strategy](#bet-strategy) + - [FilterCondition](#filtercondition) + - [Example](#example) +6. πŸ“ˆ [Analytics](#analytics) +7. πŸͺ [Migrating from an old repository (the original one)](#migrating-from-an-old-repository-the-original-one) +8. πŸͺŸ [Windows](#windows) +9. πŸ“± [Termux](#termux) +10. ⚠️ [Disclaimer](#disclaimer) + + +## Community +If you want to help with this project, please leave a star 🌟 and share it with your friends! 😎 + +If you want to offer me a coffee, I would be grateful! ❀️ + +| | | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +|Donate DOGE|`DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` _(DOGE)_| + +If you have any issues or you want to contribute, you are welcome! But please read the [CONTRIBUTING.md](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/blob/master/CONTRIBUTING.md) file. + +## Main differences from the original repository: + +- Improved logging: emojis, colors, files and much more βœ”οΈ +- Final report with all the data βœ”οΈ +- Rewritten codebase now uses classes instead of modules with global variables βœ”οΈ +- Automatic downloading of the list of followers and using it as an input βœ”οΈ +- Better 'Watch Streak' strategy in the priority system [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) βœ”οΈ +- Auto claiming [game drops](https://help.twitch.tv/s/article/mission-based-drops) from the Twitch inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) βœ”οΈ +- Placing a bet / making a prediction with your channel points [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ([@lay295](https://github.com/lay295)) βœ”οΈ +- Switchable analytics chart that shows the progress of your points with various annotations [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) βœ”οΈ +- Joining the IRC Chat to increase the watch time and get StreamElements points [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) βœ”οΈ +- [Moments](https://help.twitch.tv/s/article/moments) claiming [#182](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/issues/182) βœ”οΈ +- Notifying on `@nickname` mention in the Twitch chat [#227](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/issues/227) βœ”οΈ + +## Logs feature +### Full logs +``` +%d/%m/%y %H:%M:%S - INFO - [run]: πŸ’£ Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' +%d/%m/%y %H:%M:%S - INFO - [run]: πŸ€“ Loading data for x streamers. Please wait ... +%d/%m/%y %H:%M:%S - INFO - [set_offline]: 😴 Streamer(username=streamer-username1, channel_id=0000000, channel_points=67247) is Offline! +%d/%m/%y %H:%M:%S - INFO - [set_offline]: 😴 Streamer(username=streamer-username2, channel_id=0000000, channel_points=4240) is Offline! +%d/%m/%y %H:%M:%S - INFO - [set_offline]: 😴 Streamer(username=streamer-username3, channel_id=0000000, channel_points=61365) is Offline! +%d/%m/%y %H:%M:%S - INFO - [set_offline]: 😴 Streamer(username=streamer-username4, channel_id=0000000, channel_points=3760) is Offline! +%d/%m/%y %H:%M:%S - INFO - [set_online]: πŸ₯³ Streamer(username=streamer-username, channel_id=0000000, channel_points=61365) is Online! +%d/%m/%y %H:%M:%S - INFO - [start_bet]: πŸ”§ Start betting for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) owned by Streamer(username=streamer-username, channel_id=0000000, channel_points=61365) +%d/%m/%y %H:%M:%S - INFO - [__open_coins_menu]: πŸ”§ Open coins menu for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) +%d/%m/%y %H:%M:%S - INFO - [__click_on_bet]: πŸ”§ Click on the bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) +%d/%m/%y %H:%M:%S - INFO - [__enable_custom_bet_value]: πŸ”§ Enable input of custom value for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) +%d/%m/%y %H:%M:%S - INFO - [on_message]: ⏰ Place the bet after: 89.99s for: EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx-15c61914ef69, title=Please star this repo) +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +12 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=61377) - Reason: WATCH. +%d/%m/%y %H:%M:%S - INFO - [make_predictions]: πŸ€ Going to complete bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx-15c61914ef69, title=Please star this repo) owned by Streamer(username=streamer-username, channel_id=0000000, channel_points=61377) +%d/%m/%y %H:%M:%S - INFO - [make_predictions]: πŸ€ Place 5k channel points on: SI (BLUE), Points: 848k, Users: 190 (70.63%), Odds: 1.24 (80.65%) +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +6675 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64206) - Reason: PREDICTION. +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸ“Š EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) - Result: WIN, Points won: 6675 +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +12 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64218) - Reason: WATCH. +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +12 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64230) - Reason: WATCH. +%d/%m/%y %H:%M:%S - INFO - [claim_bonus]: 🎁 Claiming the bonus for Streamer(username=streamer-username, channel_id=0000000, channel_points=64230)! +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +60 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64290) - Reason: CLAIM. +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +12 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64326) - Reason: WATCH. +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +400 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64326) - Reason: WATCH_STREAK. +%d/%m/%y %H:%M:%S - INFO - [claim_bonus]: 🎁 Claiming the bonus for Streamer(username=streamer-username, channel_id=0000000, channel_points=64326)! +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +60 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64386) - Reason: CLAIM. +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +12 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=64398) - Reason: WATCH. +%d/%m/%y %H:%M:%S - INFO - [update_raid]: 🎭 Joining raid from Streamer(username=streamer-username, channel_id=0000000, channel_points=64398) to another-username! +%d/%m/%y %H:%M:%S - INFO - [on_message]: πŸš€ +250 β†’ Streamer(username=streamer-username, channel_id=0000000, channel_points=6845) - Reason: RAID. +``` +### Less logs +``` +%d/%m %H:%M:%S - πŸ’£ Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' +%d/%m %H:%M:%S - πŸ€“ Loading data for 13 streamers. Please wait ... +%d/%m %H:%M:%S - 😴 streamer-username1 (xxx points) is Offline! +%d/%m %H:%M:%S - 😴 streamer-username2 (xxx points) is Offline! +%d/%m %H:%M:%S - 😴 streamer-username3 (xxx points) is Offline! +%d/%m %H:%M:%S - 😴 streamer-username4 (xxx points) is Offline! +%d/%m %H:%M:%S - πŸ₯³ streamer-username (xxx points) is Online! +%d/%m %H:%M:%S - πŸ”§ Start betting for EventPrediction: Please star this repo owned by streamer-username (xxx points) +%d/%m %H:%M:%S - πŸ”§ Open coins menu for EventPrediction: Please star this repo +%d/%m %H:%M:%S - πŸ”§ Click on the bet for EventPrediction: Please star this repo +%d/%m %H:%M:%S - πŸ”§ Enable input of custom value for EventPrediction: Please star this repo +%d/%m %H:%M:%S - ⏰ Place the bet after: 89.99s EventPrediction: Please star this repo +%d/%m %H:%M:%S - πŸš€ +12 β†’ streamer-username (xxx points) - Reason: WATCH. +%d/%m %H:%M:%S - πŸ€ Going to complete bet for EventPrediction: Please star this repo owned by streamer-username (xxx points) +%d/%m %H:%M:%S - πŸ€ Place 5k channel points on: SI (BLUE), Points: 848k, Users: 190 (70.63%), Odds: 1.24 (80.65%) +%d/%m %H:%M:%S - πŸš€ +6675 β†’ streamer-username (xxx points) - Reason: PREDICTION. +%d/%m %H:%M:%S - πŸ“Š EventPrediction: Please star this repo - Result: WIN, Points won: 6675 +%d/%m %H:%M:%S - πŸš€ +12 β†’ streamer-username (xxx points) - Reason: WATCH. +%d/%m %H:%M:%S - πŸš€ +12 β†’ streamer-username (xxx points) - Reason: WATCH. +%d/%m %H:%M:%S - πŸš€ +60 β†’ streamer-username (xxx points) - Reason: CLAIM. +%d/%m %H:%M:%S - πŸš€ +12 β†’ streamer-username (xxx points) - Reason: WATCH. +%d/%m %H:%M:%S - πŸš€ +400 β†’ streamer-username (xxx points) - Reason: WATCH_STREAK. +%d/%m %H:%M:%S - πŸš€ +60 β†’ streamer-username (xxx points) - Reason: CLAIM. +%d/%m %H:%M:%S - πŸš€ +12 β†’ streamer-username (xxx points) - Reason: WATCH. +%d/%m %H:%M:%S - 🎭 Joining raid from streamer-username (xxx points) to another-username! +%d/%m %H:%M:%S - πŸš€ +250 β†’ streamer-username (xxx points) - Reason: RAID. +``` +### Final report: +``` +%d/%m/%y %H:%M:%S - πŸ›‘ End session 'f738d438-cdbc-4cd5-90c4-1517576f1299' +%d/%m/%y %H:%M:%S - πŸ“„ Logs file: /.../path/Twitch-Channel-Points-Miner-v2/logs/username.timestamp.log +%d/%m/%y %H:%M:%S - βŒ› Duration 10:29:19.547371 + +%d/%m/%y %H:%M:%S - πŸ“Š BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 +%d/%m/%y %H:%M:%S - πŸ“Š EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} +%d/%m/%y %H:%M:%S - πŸ“Š EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} +%d/%m/%y %H:%M:%S - πŸ“Š EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} + +%d/%m/%y %H:%M:%S - πŸ€– Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 +%d/%m/%y %H:%M:%S - πŸ’° CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) +%d/%m/%y %H:%M:%S - πŸ€– Streamer(username=streamer-username2, channel_id=0000000, channel_points=61365), Total points gained (after farming - before farming): 977 +%d/%m/%y %H:%M:%S - πŸ’° CLAIM(4 times, 240 gained), REFUND(1 times, 605 gained), WATCH(11 times, 132 gained) +%d/%m/%y %H:%M:%S - πŸ€– Streamer(username=streamer-username5, channel_id=0000000, channel_points=25960), Total points gained (after farming - before farming): 1680 +%d/%m/%y %H:%M:%S - πŸ’° CLAIM(17 times, 850 gained), WATCH(53 times, 530 gained) +%d/%m/%y %H:%M:%S - πŸ€– Streamer(username=streamer-username6, channel_id=0000000, channel_points=9430), Total points gained (after farming - before farming): 1120 +%d/%m/%y %H:%M:%S - πŸ’° CLAIM(14 times, 700 gained), WATCH(42 times, 420 gained), WATCH_STREAK(1 times, 450 gained) +``` + +## How to use: +First of all please create a run.py file. You can just copy [example.py](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. +```python +# -*- coding: utf-8 -*- + +import logging +from colorama import Fore +from TwitchChannelPointsMiner import TwitchChannelPointsMiner +from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette +from TwitchChannelPointsMiner.classes.Chat import ChatPresence +from TwitchChannelPointsMiner.classes.Discord import Discord +from TwitchChannelPointsMiner.classes.Webhook import Webhook +from TwitchChannelPointsMiner.classes.Telegram import Telegram +from TwitchChannelPointsMiner.classes.Settings import Priority, Events, FollowersOrder +from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings + +twitch_miner = TwitchChannelPointsMiner( + username="your-twitch-username", + password="write-your-secure-psw", # If no password will be provided, the script will ask interactively + claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on the startup + priority=[ # Custom priority in this case for example: + Priority.STREAK, # - We want first of all to catch all watch streak from all streamers + Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers + Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) + ], + enable_analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + disable_ssl_cert_verification=False, # Set to True at your own risk and only to fix SSL: CERTIFICATE_VERIFY_FAILED error + disable_at_in_nickname=False, # Set to True if you want to check for your nickname mentions in the chat even without @ sign + logger_settings=LoggerSettings( + save=True, # If you want to save logs in a file (suggested) + console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info + console_username=False, # Adds a username to every console log line if True. Also adds it to Telegram, Discord, etc. Useful when you have several accounts + auto_clear=True, # Create a file rotation handler with interval = 1D and backupCount = 7 if True (default) + time_zone="", # Set a specific time zone for console and file loggers. Use tz database names. Example: "America/Denver" + file_level=logging.DEBUG, # Level of logs - If you think the log file it's too big, use logging.INFO + emoji=True, # On Windows, we have a problem printing emoji. Set to false if you have a problem + less=False, # If you think that the logs are too verbose, set this to True + colored=True, # If you want to print colored text + color_palette=ColorPalette( # You can also create a custom palette color (for the common message). + STREAMER_online="GREEN", # Don't worry about lower/upper case. The script will parse all the values. + streamer_offline="red", # Read more in README.md + BET_wiN=Fore.MAGENTA # Color allowed are: [BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET]. + ), + telegram=Telegram( # You can omit or set to None if you don't want to receive updates on Telegram + chat_id=123456789, # Chat ID to send messages @getmyid_bot + token="123456789:shfuihreuifheuifhiu34578347", # Telegram API token @BotFather + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the chat + disable_notification=True, # Revoke the notification (sound/vibration) + ), + discord=Discord( + webhook_api="https://discord.com/api/webhooks/0123456789/0a1B2c3D4e5F6g7H8i9J", # Discord Webhook URL + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the chat + ), + webhook=Webhook( + endpoint="https://example.com/webhook", # Webhook URL + method="GET", # GET or POST + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the endpoint + ), + matrix=Matrix( + username="twitch_miner", # Matrix username (without homeserver) + password="...", # Matrix password + homeserver="matrix.org", # Matrix homeserver + room_id="...", # Room ID + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, Events.BET_LOSE], # Only these events will be sent + ), + pushover=Pushover( + userkey="YOUR-ACCOUNT-TOKEN", # Login to https://pushover.net/, the user token is on the main page + token="YOUR-APPLICATION-TOKEN", # Create a application on the website, and use the token shown in your application + priority=0, # Read more about priority here: https://pushover.net/api#priority + sound="pushover", # A list of sounds can be found here: https://pushover.net/api#sounds + events=[Events.CHAT_MENTION, Events.DROP_CLAIM], # Only these events will be sent + ) + ), + streamer_settings=StreamerSettings( + make_predictions=True, # If you want to Bet / Make prediction + follow_raid=True, # Follow raid to obtain more points + claim_drops=True, # We can't filter rewards base on stream. Set to False for skip viewing counter increase and you will never obtain a drop reward from this script. Issue #21 + claim_moments=True, # If set to True, https://help.twitch.tv/s/article/moments will be claimed when available + watch_streak=True, # If a streamer go online change the priority of streamers array and catch the watch screak. Issue #11 + chat=ChatPresence.ONLINE, # Join irc chat to increase watch-time [ALWAYS, NEVER, ONLINE, OFFLINE] + bet=BetSettings( + strategy=Strategy.SMART, # Choose you strategy! + percentage=5, # Place the x% of your channel points + percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART strategy) + max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value + stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33 + delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer + delay=6, + minimum_points=20000, # Place the bet only if we have at least 20k points. Issue #113 + filter_condition=FilterCondition( + by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value + value=800 + ) + ) + ) +) + +# You can customize the settings for each streamer. If not settings were provided, the script would use the streamer_settings from TwitchChannelPointsMiner. +# If no streamer_settings are provided in TwitchChannelPointsMiner the script will use default settings. +# The streamers array can be a String -> username or Streamer instance. + +# The settings priority are: settings in mine function, settings in TwitchChannelPointsMiner instance, default settings. +# For example, if in the mine function you don't provide any value for 'make_prediction' but you have set it on TwitchChannelPointsMiner instance, the script will take the value from here. +# If you haven't set any value even in the instance the default one will be used + +#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server (replit: host="0.0.0.0") + +twitch_miner.mine( + [ + Streamer("streamer-username01", settings=StreamerSettings(make_predictions=True , follow_raid=False , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=True, percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.LTE, value=800 ) ) )), + Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , stealth_mode=False, percentage_gap=20 , max_points=1234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_POINTS, where=Condition.GTE, value=250 ) ) )), + Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=False, percentage_gap=30 , max_points=50000 , filter_condition=FilterCondition(by=OutcomeKeys.ODDS, where=Condition.LT, value=300 ) ) )), + Streamer("streamer-username04", settings=StreamerSettings(make_predictions=False , follow_raid=True , watch_streak=True )), + Streamer("streamer-username05", settings=StreamerSettings(make_predictions=True , follow_raid=True , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.HIGH_ODDS , percentage=7 , stealth_mode=True, percentage_gap=20 , max_points=90 , filter_condition=FilterCondition(by=OutcomeKeys.PERCENTAGE_USERS, where=Condition.GTE, value=300 ) ) )), + Streamer("streamer-username06"), + Streamer("streamer-username07"), + Streamer("streamer-username08"), + "streamer-username09", + "streamer-username10", + "streamer-username11" + ], # Array of streamers (order = priority) + followers=False, # Automatic download the list of your followers + followers_order=FollowersOrder.ASC # Sort the followers list by follow date. ASC or DESC +) +``` +You can also use all the default values except for your username obv. Short version: +```python +from TwitchChannelPointsMiner import TwitchChannelPointsMiner +from TwitchChannelPointsMiner.classes.Settings import FollowersOrder +twitch_miner = TwitchChannelPointsMiner("your-twitch-username") +twitch_miner.mine(["streamer1", "streamer2"]) # Array of streamers OR +twitch_miner.mine(followers=True, followers_order=FollowersOrder.ASC) # Automatic use the followers list OR +twitch_miner.mine(["streamer1", "streamer2"], followers=True, followers_order=FollowersOrder.DESC) # Mixed +``` +If you follow so many streamers on Twitch, but you don't want to mine points for all of them, you can blacklist the users with the `blacklist` keyword. [#94](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/94) +```python +from TwitchChannelPointsMiner import TwitchChannelPointsMiner +twitch_miner = TwitchChannelPointsMiner("your-twitch-username") +twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) # Blacklist example +``` + +### By cloning the repository +1. Clone this repository `git clone https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2` +2. Install all the requirements `pip install -r requirements.txt` . If you have problems with requirements, make sure to have at least Python3.6. You could also try to create a _virtualenv_ and then install all the requirements +```sh +pip install virtualenv +virtualenv -p python3 venv +source venv/bin/activate +pip install -r requirements.txt +``` + +Start mining! `python run.py` πŸ₯³ + +### Docker + +#### Docker Hub +Official Docker images are on https://hub.docker.com/r/rdavidoff/twitch-channel-points-miner-v2 for `linux/amd64`, `linux/arm64` and `linux/arm/v7`. + +The following file is mounted : + +- run.py : this is your starter script with your configuration + +These folders are mounted : + +- analytics : to save the analytics data +- cookies : to provide login information +- logs : to keep logs outside of container + +**Example using docker-compose:** + +```yml +version: "3.9" + +services: + miner: + image: rdavidoff/twitch-channel-points-miner-v2 + stdin_open: true + tty: true + environment: + - TERM=xterm-256color + volumes: + - ./analytics:/usr/src/app/analytics + - ./cookies:/usr/src/app/cookies + - ./logs:/usr/src/app/logs + - ./run.py:/usr/src/app/run.py:ro + ports: + - "5000:5000" +``` + +**Example with docker run:** +```sh +docker run \ + -v $(pwd)/analytics:/usr/src/app/analytics \ + -v $(pwd)/cookies:/usr/src/app/cookies \ + -v $(pwd)/logs:/usr/src/app/logs \ + -v $(pwd)/run.py:/usr/src/app/run.py:ro \ + -p 5000:5000 \ + rdavidoff/twitch-channel-points-miner-v2 +``` + +`$(pwd)` Could not work on Windows (cmd), please use the absolute path instead, like: `/path/of/your/cookies:/usr/src/app/cookies`. + +The correct solution for Windows lies in the correct command line: `docker run -v C:\Absolute\Path\To\Twitch-Channel-Points-Miner-v2\run.py:/usr/src/app/run.py:ro rdavidoff/twitch-channel-points-miner-v2`. + +`run.py` MUST be mounted as a volume (`-v`). + +If you don't mount the volume for the analytics (or cookies or logs) folder, the folder will be automatically created on the Docker container, and you will lose all the data when it is stopped. + +If you don't have a cookie or it's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like + +```sh +docker run --name user1 -v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 rdavidoff/twitch-channel-points-miner-v2 +``` + +```sh +docker run --name user2 -v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 rdavidoff/twitch-channel-points-miner-v2 +``` + +#### Portainer + +[Link](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/wiki/Deploy-Docker-container-in-Portainer) to the illustrated guide on how to deploy a Docker container in Portainer. + +### Replit + +Official Repl: https://replit.com/@rdavydov/Twitch-Channel-Points-Miner-v2 + +Provided "as is" with no support. Testing purposes only. Updates may be delayed. + +### Limits +_**Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority.**_ + +Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` you can choose to download the followers sorted by follow date (ASC or DESC). + +## Settings +Most of the settings are self-explained and are commented on in the example. +You can watch only two streamers per time. With `priority` settings, you can select which streamers watch by use priority. You can use an array of priority or single item. I suggest using at least one priority from `ORDER`, `POINTS_ASCENDING`, `POINTS_DESCEDING` because, for example, If you set only `STREAK` after catch all watch streak, the script will stop to watch streamers. +Available values are the following: + - `STREAK` - Catch the watch streak from all streamers + - `DROPS` - Claim all drops from streamers with drops tags enabled + - `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) + - `ORDER` - Following the order of the list + - `POINTS_ASCENDING` - On top the streamers with the lowest points + - `POINTS_DESCEDING` - On top the streamers with the highest points + +You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. + +### LoggerSettings +| Key | Type | Default | Description | +|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `save` | bool | True | If you want to save logs in file (suggested) | +| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | +| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | +| `console_username`| bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602)| +| `time_zone`| str | None | Set a specific time zone for console and file loggers. Use tz database names. Example: "America/Denver" https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/issues/205| +| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | +| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | +| `telegram` | Telegram | None | (Optional) Receive Telegram updates for multiple events list [#233](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/233) | +| `discord` | Discord | None | (Optional) Receive Discord updates for multiple events list [#320](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/320) | + +#### Color Palette +Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. You can change the colors of all `Events` enum class. The colors allowed are all the Fore color from Colorama: `BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.` +The script was developed to handle all the human error, lower-case upper case and more, but I want to suggest using the following code-style +```python +from colorama import Fore +ColorPalette( + STREAMER_ONLINE = Fore.GREEN, + STREAMER_OFFLINE = Fore.RED, + GAIN_FOR_RAID = Fore.YELLOW, + GAIN_FOR_CLAIM = Fore.YELLOW, + GAIN_FOR_WATCH = Fore.YELLOW, + GAIN_FOR_WATCH_STREAK = Fore.YELLOW, + BET_WIN = Fore.GREEN, + BET_LOSE = Fore.RED, + BET_REFUND = Fore.RESET, + BET_FILTERS = Fore.MAGENTA, + BET_GENERAL = Fore.BLUE, + BET_FAILED = Fore.RED, +) +``` + +#### Telegram +If you want to receive logs update on Telegram, initiate a new Telegram class, else omit this parameter or set as None. +1. Create a bot with [@BotFather](https://t.me/botfather) +2. Get you `chat_id` with [@getmyid_bot](https://t.me/getmyid_bot) + +| Key | Type | Default | Description | +|----------------------- |----------------- |--------- |------------------------------------------------------------------- | +| `chat_id` | int | | Chat ID to send messages @getmyid_bot | +| `token` | string | | Telegram API token @BotFather | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | +| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | + + +```python +Telegram( + chat_id=123456789, + token="123456789:shfuihreuifheuifhiu34578347", + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], + disable_notification=True, +) +``` + +#### Discord +If you want to receive log updates on Discord initialize a new Discord class, else leave omit this parameter or set it as None [YT Video](https://www.youtube.com/watch?v=fKksxz2Gdnc) +1. Go to the Server you want to receive updates +2. Click "Edit Channel" +3. Click "Integrations" +4. Click "Webhooks" +5. Click "New Webhook" +6. Name it if you want +7. Click on "Copy Webhook URL" + + +| Key | Type | Default | Description | +|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | +| `webhook_api` | string | | Discord webhook URL | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | + +```python +Discord( + webhook_api="https://discord.com/api/webhooks/0123456789/0a1B2c3D4e5F6g7H8i9J", + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], +) +``` + +#### Generic Webhook +You can use generic webhook + +| Key | Type | Default | Description | +|----------------------- |------------------|-----------|------------------------------------------------------------------- | +| `endpoint` | string | | webhook url | +| `method` | string | | `POST` or `GET` | +| `events` | list | | Only these events will be sent to the endpoint. Array of Event. or str | + +```python +Webhook( + endpoint="https://example.com/webhook", + method="GET", + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], +) +``` + + +#### Events + - `STREAMER_ONLINE` + - `STREAMER_OFFLINE` + - `GAIN_FOR_RAID` + - `GAIN_FOR_CLAIM` + - `GAIN_FOR_WATCH` + - `BET_WIN` + - `BET_LOSE` + - `BET_REFUND` + - `BET_FILTERS` + - `BET_GENERAL` + - `BET_FAILED` + - `BET_START` + - `BONUS_CLAIM` + - `MOMENT_CLAIM` + - `JOIN_RAID` + - `DROP_CLAIM` + - `DROP_STATUS` + - `CHAT_MENTION` + +### StreamerSettings +| Key | Type | Default | Description | +|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `claim_moments` | bool | True | If set to True, [moments](https://help.twitch.tv/s/article/moments) will be claimed when available | +| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `bet` | BetSettings | | Rules to follow for the bet | +| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | + +Allowed values for `chat` are: +- `ALWAYS` Join in IRC chat and never leave +- `NEVER` Never join IRC chat +- `ONLINE` Partecipate to IRC chat if the streamer is online (leave if offline) +- `OFFLINE` Partecipate to IRC chat if the streamer is offline (leave if online) + +### BetSettings +| Key | Type | Default | Description | +|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `strategy` | Strategy | SMART | Choose your strategy! See below for more info | +| `percentage` | int | 5 | Place the x% of your channel points | +| `percentage_gap` | int | 20 | Gap difference between outcomesA and outcomesB (for SMART stragegy) | +| `max_points` | int | 50000 | If the x percentage of your channel points is GT bet_max_points set this value | +| `stealth_mode` | bool | False | If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points [#33](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/33) | +| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | +| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | + +#### Bet strategy + +- **MOST_VOTED**: Select the option most voted based on users count +- **HIGH_ODDS**: Select the option with the highest odds +- **PERCENTAGE**: Select the option with the highest percentage based on odds (It's the same that show Twitch) - Should be the same as select LOWEST_ODDS +- **SMART_MONEY**: Select the option with the highest points placed. [#331](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/331) +- **SMART**: If the majority in percent chose an option, then follow the other users, otherwise select the option with the highest odds + +![Screenshot](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/prediction.png) + +Here a concrete example: + +- **MOST_VOTED**: 21 Users have select **'over 7.5'**, instead of 9 'under 7.5' +- **HIGH_ODDS**: The highest odd is 2.27 on **'over 7.5'** vs 1.79 on 'under 7.5' +- **PERCENTAGE**: The highest percentage is 56% for **'under 7.5'** +- **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. + +In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' +### FilterCondition +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | + +Allowed values for `by` are: +- `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] +- `ODDS_PERCENTAGE` (no sum) [Doesn't make sense to sum odds] +- `ODDS` (no sum) [Doesn't make sense to sum odds] +- `DECISION_USERS` (no sum) +- `DECISION_POINTS` (no sum) +- `TOP_POINTS` (no sum) [Doesn't make sense to the top points of both sides] +- `TOTAL_USERS` (sum) +- `TOTAL_POINTS` (sum) + +Allowed values for `where` are: `GT, LT, GTE, LTE` + +#### Example +- If you want to place the bet ONLY if the total of users participants in the bet is greater than 200 +`FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.GT, value=200)` +- If you want to place the bet ONLY if the winning odd of your decision is greater than or equal to 1.3 +`FilterCondition(by=OutcomeKeys.ODDS, where=Condition.GTE, value=1.3)` +- If you want to place the bet ONLY if the highest bet is lower than 2000 +`FilterCondition(by=OutcomeKeys.TOP_POINTS, where=Condition.LT, value=2000)` + +### DelayMode + +- **FROM_START**: Will wait `delay` seconds from when the bet was opened +- **FROM_END**: Will until there is `delay` seconds left to place the bet +- **PERCENTAGE**: Will place the bet when `delay` percent of the set timer is elapsed + +Here's a concrete example. Let's suppose we have a bet that is opened with a timer of 10 minutes: + +- **FROM_START** with `delay=20`: The bet will be placed 20s after the bet is opened +- **FROM_END** with `delay=20`: The bet will be placed 20s before the end of the bet (so 9mins 40s after the bet is opened) +- **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened) + +## Analytics +We have recently introduced a little frontend where you can show with a chart you points trend. The script will spawn a Flask web-server on your machine where you can select binding address and port. +The chart provides some annotation to handle the prediction and watch strike events. Usually annotation are used to notice big increase / decrease of points. If you want to can disable annotations. +On each (x, y) points Its present a tooltip that show points, date time and reason of points gained / lost. This web page was just a funny idea, and it is not intended to use for a professional usage. +If you want you can toggle the dark theme with the dedicated checkbox. + +| Light theme | Dark theme | +| ----------- | ---------- | +| ![Light theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-light.png) | ![Dark theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-dark.png) | + +For use this feature just call the `analytics()` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) +The chart will be autofreshed each `refresh` minutes. If you want to connect from one to second machine that have that webpanel you have to use `0.0.0.0` instead of `127.0.0.1`. With the `days_ago` arg you can select how many days you want to show by default in your analytics graph. +```python +from TwitchChannelPointsMiner import TwitchChannelPointsMiner +twitch_miner = TwitchChannelPointsMiner("your-twitch-username") +twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Analytics web-server +twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) +``` + +### `enable_analytics` option in `twitch_minerfile` toggles Analytics needed for the `analytics()` method + +Disabling Analytics significantly reduces memory consumption and saves some disk space by not creating and writing `/analytics/*.json`. + +Set this option to `True` if you need Analytics. Otherwise set this option to `False` (default value). + +## Migrating from an old repository (the original one): +If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` +``` +. ++-- run.py ++-- cookies +| +-- your-twitch-username.pkl +``` + +## Windows +Other users have find multiple problems on Windows. Suggestions are: + - Stop using Windows :stuck_out_tongue_closed_eyes: + - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` + +Other useful info can be found here: +- https://github.com/gottagofaster236/Twitch-Channel-Points-Miner/issues/31 +- https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/55 + +You can also follow this [video tutorial](https://www.youtube.com/watch?v=0VkM7NOZkuA). + +## Termux +**1. Upgrade packages** +``` +pkg upgrade +``` + +**2. Install packages to Termux** +``` +pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib binutils tur-repo +LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow +``` +Note: `pkg install tur-repo` will basically enable the [user repository](https://github.com/termux-user-repository/tur) _(Very similar to Arch AUR)_ and `python-pandas` pre-compiled package comes exactly from this repository. + +**3. Install pandas** +``` +pkg install python-pandas +``` + +**4. Clone this repository** + +`git clone https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2` + +**5. Go to the miner's directory** + +`cd Twitch-Channel-Points-Miner-v2` + +**6. Configure your miner on your preferences by typing** + +`nano example.py` + +**7. Rename file name (optional)** + +`mv example.py run.py` + +**8. Install packages** +``` +pip install -r requirements.txt +pip install Twitch-Channel-Points-Miner-v2 +``` + +**9. Run the miner!** + +`python run.py` + +Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) + +**Note** + +If you can't install `cryptography`, please try: + +`export RUSTFLAGS=" -C lto=no" && export CARGO_BUILD_TARGET="$(rustc -vV | sed -n 's|host: ||p')" && pip install cryptography` + +⚠️ Installation of `pandas`, `maturin` and `cryptography` takes a long time. + +## Disclaimer +This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. diff --git a/TRAFFIC.md b/TRAFFIC.md new file mode 100644 index 0000000..576f91e --- /dev/null +++ b/TRAFFIC.md @@ -0,0 +1,11 @@ + + + **Markdown** + ```markdown +[![GitHub Traffic](https://img.shields.io/badge/dynamic/json?color=success&label=Views&query=count&url=https://gist.githubusercontent.com/rdavydov/ad9a3c6a8d9c322f9a6b62781ea94a93/raw/traffic.json&logo=github)](https://github.com/MShawon/github-clone-count-badge) + + ``` + **HTML** + ```html +GitHub Traffic +``` diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py new file mode 100644 index 0000000..f937351 --- /dev/null +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -0,0 +1,496 @@ +# -*- coding: utf-8 -*- + +import logging +import os +import random +import signal +import sys +import threading +import time +import uuid +from datetime import datetime +from pathlib import Path + +from TwitchChannelPointsMiner.classes.Chat import ChatPresence, ThreadChat +from TwitchChannelPointsMiner.classes.entities.PubsubTopic import PubsubTopic +from TwitchChannelPointsMiner.classes.entities.Streamer import ( + Streamer, + StreamerSettings, +) +from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException +from TwitchChannelPointsMiner.classes.Settings import FollowersOrder, Priority, Settings +from TwitchChannelPointsMiner.classes.Twitch import Twitch +from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool +from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers +from TwitchChannelPointsMiner.utils import ( + _millify, + at_least_one_value_in_settings_is, + check_versions, + get_user_agent, + internet_connection_available, + set_default_settings, +) + +# Suppress: +# - chardet.charsetprober - [feed] +# - chardet.charsetprober - [get_confidence] +# - requests - [Starting new HTTPS connection (1)] +# - Flask (werkzeug) logs +# - irc.client - [process_data] +# - irc.client - [_dispatcher] +# - irc.client - [_handle_message] +logging.getLogger("chardet.charsetprober").setLevel(logging.ERROR) +logging.getLogger("requests").setLevel(logging.ERROR) +logging.getLogger("werkzeug").setLevel(logging.ERROR) +logging.getLogger("irc.client").setLevel(logging.ERROR) +logging.getLogger("seleniumwire").setLevel(logging.ERROR) +logging.getLogger("websocket").setLevel(logging.ERROR) + +logger = logging.getLogger(__name__) + + +class TwitchChannelPointsMiner: + __slots__ = [ + "username", + "twitch", + "claim_drops_startup", + "enable_analytics", + "disable_ssl_cert_verification", + "disable_at_in_nickname", + "priority", + "streamers", + "events_predictions", + "minute_watcher_thread", + "sync_campaigns_thread", + "ws_pool", + "session_id", + "running", + "start_datetime", + "original_streamers", + "logs_file", + "queue_listener", + ] + + def __init__( + self, + username: str, + password: str = None, + claim_drops_startup: bool = False, + enable_analytics: bool = False, + disable_ssl_cert_verification: bool = False, + disable_at_in_nickname: bool = False, + # Settings for logging and selenium as you can see. + priority: list = [Priority.STREAK, Priority.DROPS, Priority.ORDER], + # This settings will be global shared trought Settings class + logger_settings: LoggerSettings = LoggerSettings(), + # Default values for all streamers + streamer_settings: StreamerSettings = StreamerSettings(), + ): + # Fixes TypeError: 'NoneType' object is not subscriptable + if not username or username == "your-twitch-username": + logger.error( + "Please edit your runner file (usually run.py) and try again.") + logger.error("No username, exiting...") + sys.exit(0) + + # This disables certificate verification and allows the connection to proceed, but also makes it vulnerable to man-in-the-middle (MITM) attacks. + Settings.disable_ssl_cert_verification = disable_ssl_cert_verification + + Settings.disable_at_in_nickname = disable_at_in_nickname + + import socket + + def is_connected(): + try: + # resolve the IP address of the Twitch.tv domain name + socket.gethostbyname("twitch.tv") + return True + except OSError: + pass + return False + + # check for Twitch.tv connectivity every 5 seconds + error_printed = False + while not is_connected(): + if not error_printed: + logger.error("Waiting for Twitch.tv connectivity...") + error_printed = True + time.sleep(5) + + # Analytics switch + Settings.enable_analytics = enable_analytics + + if enable_analytics is True: + Settings.analytics_path = os.path.join( + Path().absolute(), "analytics", username + ) + Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) + + self.username = username + + # Set as global config + Settings.logger = logger_settings + + # Init as default all the missing values + streamer_settings.default() + streamer_settings.bet.default() + Settings.streamer_settings = streamer_settings + + # user_agent = get_user_agent("FIREFOX") + user_agent = get_user_agent("CHROME") + self.twitch = Twitch(self.username, user_agent, password) + + self.claim_drops_startup = claim_drops_startup + self.priority = priority if isinstance(priority, list) else [priority] + + self.streamers = [] + self.events_predictions = {} + self.minute_watcher_thread = None + self.sync_campaigns_thread = None + self.ws_pool = None + + self.session_id = str(uuid.uuid4()) + self.running = False + self.start_datetime = None + self.original_streamers = [] + + self.logs_file, self.queue_listener = configure_loggers( + self.username, logger_settings + ) + + # Check for the latest version of the script + current_version, github_version = check_versions() + + logger.info( + f"Twitch Channel Points Miner v2-{current_version} (fork by rdavydov)" + ) + logger.info( + "https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2") + + if github_version == "0.0.0": + logger.error( + "Unable to detect if you have the latest version of this script" + ) + elif current_version != github_version: + logger.info( + f"You are running version {current_version} of this script") + logger.info(f"The latest version on GitHub is {github_version}") + + for sign in [signal.SIGINT, signal.SIGSEGV, signal.SIGTERM]: + signal.signal(sign, self.end) + + def analytics( + self, + host: str = "127.0.0.1", + port: int = 5000, + refresh: int = 5, + days_ago: int = 7, + ): + # Analytics switch + if Settings.enable_analytics is True: + from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer + + http_server = AnalyticsServer( + host=host, port=port, refresh=refresh, days_ago=days_ago, username=self.username + ) + http_server.daemon = True + http_server.name = "Analytics Thread" + http_server.start() + else: + logger.error( + "Can't start analytics(), please set enable_analytics=True") + + def mine( + self, + streamers: list = [], + blacklist: list = [], + followers: bool = False, + followers_order: FollowersOrder = FollowersOrder.ASC, + ): + self.run(streamers=streamers, blacklist=blacklist, followers=followers) + + def run( + self, + streamers: list = [], + blacklist: list = [], + followers: bool = False, + followers_order: FollowersOrder = FollowersOrder.ASC, + ): + if self.running: + logger.error("You can't start multiple sessions of this instance!") + else: + logger.info( + f"Start session: '{self.session_id}'", extra={"emoji": ":bomb:"} + ) + self.running = True + self.start_datetime = datetime.now() + + self.twitch.login() + + if self.claim_drops_startup is True: + self.twitch.claim_all_drops_from_inventory() + + streamers_name: list = [] + streamers_dict: dict = {} + + for streamer in streamers: + username = ( + streamer.username + if isinstance(streamer, Streamer) + else streamer.lower().strip() + ) + if username not in blacklist: + streamers_name.append(username) + streamers_dict[username] = streamer + + if followers is True: + followers_array = self.twitch.get_followers( + order=followers_order) + logger.info( + f"Load {len(followers_array)} followers from your profile!", + extra={"emoji": ":clipboard:"}, + ) + for username in followers_array: + if username not in streamers_dict and username not in blacklist: + streamers_name.append(username) + streamers_dict[username] = username.lower().strip() + + logger.info( + f"Loading data for {len(streamers_name)} streamers. Please wait...", + extra={"emoji": ":nerd_face:"}, + ) + for username in streamers_name: + if username in streamers_name: + time.sleep(random.uniform(0.3, 0.7)) + try: + streamer = ( + streamers_dict[username] + if isinstance(streamers_dict[username], Streamer) is True + else Streamer(username) + ) + streamer.channel_id = self.twitch.get_channel_id( + username) + streamer.settings = set_default_settings( + streamer.settings, Settings.streamer_settings + ) + streamer.settings.bet = set_default_settings( + streamer.settings.bet, Settings.streamer_settings.bet + ) + if streamer.settings.chat != ChatPresence.NEVER: + streamer.irc_chat = ThreadChat( + self.username, + self.twitch.twitch_login.get_auth_token(), + streamer.username, + ) + self.streamers.append(streamer) + except StreamerDoesNotExistException: + logger.info( + f"Streamer {username} does not exist", + extra={"emoji": ":cry:"}, + ) + + # Populate the streamers with default values. + # 1. Load channel points and auto-claim bonus + # 2. Check if streamers are online + # 3. DEACTIVATED: Check if the user is a moderator. (was used before the 5th of April 2021 to deactivate predictions) + for streamer in self.streamers: + time.sleep(random.uniform(0.3, 0.7)) + self.twitch.load_channel_points_context(streamer) + self.twitch.check_streamer_online(streamer) + # self.twitch.viewer_is_mod(streamer) + + self.original_streamers = [ + streamer.channel_points for streamer in self.streamers + ] + + # If we have at least one streamer with settings = make_predictions True + make_predictions = at_least_one_value_in_settings_is( + self.streamers, "make_predictions", True + ) + + # If we have at least one streamer with settings = claim_drops True + # Spawn a thread for sync inventory and dashboard + if ( + at_least_one_value_in_settings_is( + self.streamers, "claim_drops", True) + is True + ): + self.sync_campaigns_thread = threading.Thread( + target=self.twitch.sync_campaigns, + args=(self.streamers,), + ) + self.sync_campaigns_thread.name = "Sync campaigns/inventory" + self.sync_campaigns_thread.start() + time.sleep(30) + + self.minute_watcher_thread = threading.Thread( + target=self.twitch.send_minute_watched_events, + args=(self.streamers, self.priority), + ) + self.minute_watcher_thread.name = "Minute watcher" + self.minute_watcher_thread.start() + + self.ws_pool = WebSocketsPool( + twitch=self.twitch, + streamers=self.streamers, + events_predictions=self.events_predictions, + ) + + # Subscribe to community-points-user. Get update for points spent or gains + user_id = self.twitch.twitch_login.get_user_id() + # print(f"!!!!!!!!!!!!!! USER_ID: {user_id}") + + # Fixes 'ERR_BADAUTH' + if not user_id: + logger.error("No user_id, exiting...") + self.end(0, 0) + + self.ws_pool.submit( + PubsubTopic( + "community-points-user-v1", + user_id=user_id, + ) + ) + + # Going to subscribe to predictions-user-v1. Get update when we place a new prediction (confirm) + if make_predictions is True: + self.ws_pool.submit( + PubsubTopic( + "predictions-user-v1", + user_id=user_id, + ) + ) + + for streamer in self.streamers: + self.ws_pool.submit( + PubsubTopic("video-playback-by-id", streamer=streamer) + ) + + if streamer.settings.follow_raid is True: + self.ws_pool.submit(PubsubTopic("raid", streamer=streamer)) + + if streamer.settings.make_predictions is True: + self.ws_pool.submit( + PubsubTopic("predictions-channel-v1", + streamer=streamer) + ) + + if streamer.settings.claim_moments is True: + self.ws_pool.submit( + PubsubTopic("community-moments-channel-v1", + streamer=streamer) + ) + + refresh_context = time.time() + while self.running: + time.sleep(random.uniform(20, 60)) + # Do an external control for WebSocket. Check if the thread is running + # Check if is not None because maybe we have already created a new connection on array+1 and now index is None + for index in range(0, len(self.ws_pool.ws)): + if ( + self.ws_pool.ws[index].is_reconnecting is False + and self.ws_pool.ws[index].elapsed_last_ping() > 10 + and internet_connection_available() is True + ): + logger.info( + f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..." + ) + WebSocketsPool.handle_reconnection( + self.ws_pool.ws[index]) + + if ((time.time() - refresh_context) // 60) >= 30: + refresh_context = time.time() + for index in range(0, len(self.streamers)): + if self.streamers[index].is_online: + self.twitch.load_channel_points_context( + self.streamers[index] + ) + + def end(self, signum, frame): + logger.info("CTRL+C Detected! Please wait just a moment!") + + for streamer in self.streamers: + if ( + streamer.irc_chat is not None + and streamer.settings.chat != ChatPresence.NEVER + ): + streamer.leave_chat() + if streamer.irc_chat.is_alive() is True: + streamer.irc_chat.join() + + self.running = self.twitch.running = False + if self.ws_pool is not None: + self.ws_pool.end() + + if self.minute_watcher_thread is not None: + self.minute_watcher_thread.join() + + if self.sync_campaigns_thread is not None: + self.sync_campaigns_thread.join() + + # Check if all the mutex are unlocked. + # Prevent breaks of .json file + for streamer in self.streamers: + if streamer.mutex.locked(): + streamer.mutex.acquire() + streamer.mutex.release() + + self.__print_report() + + # Stop the queue listener to make sure all messages have been logged + self.queue_listener.stop() + + sys.exit(0) + + def __print_report(self): + print("\n") + logger.info( + f"Ending session: '{self.session_id}'", extra={"emoji": ":stop_sign:"} + ) + if self.logs_file is not None: + logger.info( + f"Logs file: {self.logs_file}", extra={"emoji": ":page_facing_up:"} + ) + logger.info( + f"Duration {datetime.now() - self.start_datetime}", + extra={"emoji": ":hourglass:"}, + ) + + if self.events_predictions != {}: + print("") + for event_id in self.events_predictions: + event = self.events_predictions[event_id] + if ( + event.bet_confirmed is True + and event.streamer.settings.make_predictions is True + ): + logger.info( + f"{event.streamer.settings.bet}", + extra={"emoji": ":wrench:"}, + ) + if event.streamer.settings.bet.filter_condition is not None: + logger.info( + f"{event.streamer.settings.bet.filter_condition}", + extra={"emoji": ":pushpin:"}, + ) + logger.info( + f"{event.print_recap()}", + extra={"emoji": ":bar_chart:"}, + ) + + print("") + for streamer_index in range(0, len(self.streamers)): + if self.streamers[streamer_index].history != {}: + gained = ( + self.streamers[streamer_index].channel_points + - self.original_streamers[streamer_index] + ) + logger.info( + f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(gained)}", + extra={"emoji": ":robot:"}, + ) + if self.streamers[streamer_index].history != {}: + logger.info( + f"{self.streamers[streamer_index].print_history()}", + extra={"emoji": ":moneybag:"}, + ) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py new file mode 100644 index 0000000..fbd8841 --- /dev/null +++ b/TwitchChannelPointsMiner/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +__version__ = "1.9.3" +from .TwitchChannelPointsMiner import TwitchChannelPointsMiner + +__all__ = [ + "TwitchChannelPointsMiner", +] diff --git a/TwitchChannelPointsMiner/classes/AnalyticsServer.py b/TwitchChannelPointsMiner/classes/AnalyticsServer.py new file mode 100644 index 0000000..0aabbe1 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/AnalyticsServer.py @@ -0,0 +1,295 @@ +import json +import logging +import os +from datetime import datetime +from pathlib import Path +from threading import Thread + +import pandas as pd +from flask import Flask, Response, cli, render_template, request + +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import download_file + +cli.show_server_banner = lambda *_: None +logger = logging.getLogger(__name__) + + +def streamers_available(): + path = Settings.analytics_path + return [ + f + for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) and f.endswith(".json") + ] + + +def aggregate(df, freq="30Min"): + df_base_events = df[(df.z == "Watch") | (df.z == "Claim")] + df_other_events = df[(df.z != "Watch") & (df.z != "Claim")] + + be = df_base_events.groupby( + [pd.Grouper(freq=freq, key="datetime"), "z"]).max() + be = be.reset_index() + + oe = df_other_events.groupby( + [pd.Grouper(freq=freq, key="datetime"), "z"]).max() + oe = oe.reset_index() + + result = pd.concat([be, oe]) + return result + + +def filter_datas(start_date, end_date, datas): + # Note: https://stackoverflow.com/questions/4676195/why-do-i-need-to-multiply-unix-timestamps-by-1000-in-javascript + start_date = ( + datetime.strptime(start_date, "%Y-%m-%d").timestamp() * 1000 + if start_date is not None + else 0 + ) + end_date = ( + datetime.strptime(end_date, "%Y-%m-%d") + if end_date is not None + else datetime.now() + ).replace(hour=23, minute=59, second=59).timestamp() * 1000 + + original_series = datas["series"] + + if "series" in datas: + df = pd.DataFrame(datas["series"]) + df["datetime"] = pd.to_datetime(df.x // 1000, unit="s") + + df = df[(df.x >= start_date) & (df.x <= end_date)] + + datas["series"] = ( + df.drop(columns="datetime") + .sort_values(by=["x", "y"], ascending=True) + .to_dict("records") + ) + else: + datas["series"] = [] + + # If no data is found within the timeframe, that usually means the streamer hasn't streamed within that timeframe + # We create a series that shows up as a straight line on the dashboard, with 'No Stream' as labels + if len(datas["series"]) == 0: + new_end_date = start_date + new_start_date = 0 + df = pd.DataFrame(original_series) + df["datetime"] = pd.to_datetime(df.x // 1000, unit="s") + + # Attempt to get the last known balance from before the provided timeframe + df = df[(df.x >= new_start_date) & (df.x <= new_end_date)] + last_balance = df.drop(columns="datetime").sort_values( + by=["x", "y"], ascending=True).to_dict("records")[-1]['y'] + + datas["series"] = [{'x': start_date, 'y': last_balance, 'z': 'No Stream'}, { + 'x': end_date, 'y': last_balance, 'z': 'No Stream'}] + + if "annotations" in datas: + df = pd.DataFrame(datas["annotations"]) + df["datetime"] = pd.to_datetime(df.x // 1000, unit="s") + + df = df[(df.x >= start_date) & (df.x <= end_date)] + + datas["annotations"] = ( + df.drop(columns="datetime") + .sort_values(by="x", ascending=True) + .to_dict("records") + ) + else: + datas["annotations"] = [] + + return datas + + +def read_json(streamer, return_response=True): + start_date = request.args.get("startDate", type=str) + end_date = request.args.get("endDate", type=str) + + path = Settings.analytics_path + streamer = streamer if streamer.endswith(".json") else f"{streamer}.json" + + # Check if the file exists before attempting to read it + if not os.path.exists(os.path.join(path, streamer)): + error_message = f"File '{streamer}' not found." + logger.error(error_message) + if return_response: + return Response(json.dumps({"error": error_message}), status=404, mimetype="application/json") + else: + return {"error": error_message} + + try: + with open(os.path.join(path, streamer), 'r') as file: + data = json.load(file) + except json.JSONDecodeError as e: + error_message = f"Error decoding JSON in file '{streamer}': {str(e)}" + logger.error(error_message) + if return_response: + return Response(json.dumps({"error": error_message}), status=500, mimetype="application/json") + else: + return {"error": error_message} + + # Handle filtering data, if applicable + filtered_data = filter_datas(start_date, end_date, data) + if return_response: + return Response(json.dumps(filtered_data), status=200, mimetype="application/json") + else: + return filtered_data + + +def get_challenge_points(streamer): + datas = read_json(streamer, return_response=False) + if "series" in datas and datas["series"]: + return datas["series"][-1]["y"] + return 0 # Default value when 'series' key is not found or empty + + +def get_last_activity(streamer): + datas = read_json(streamer, return_response=False) + if "series" in datas and datas["series"]: + return datas["series"][-1]["x"] + return 0 # Default value when 'series' key is not found or empty + + +def json_all(): + return Response( + json.dumps( + [ + { + "name": streamer.strip(".json"), + "data": read_json(streamer, return_response=False), + } + for streamer in streamers_available() + ] + ), + status=200, + mimetype="application/json", + ) + + +def index(refresh=5, days_ago=7): + return render_template( + "charts.html", + refresh=(refresh * 60 * 1000), + daysAgo=days_ago, + ) + + +def streamers(): + return Response( + json.dumps( + [ + {"name": s, "points": get_challenge_points( + s), "last_activity": get_last_activity(s)} + for s in sorted(streamers_available()) + ] + ), + status=200, + mimetype="application/json", + ) + + +def download_assets(assets_folder, required_files): + Path(assets_folder).mkdir(parents=True, exist_ok=True) + logger.info(f"Downloading assets to {assets_folder}") + + for f in required_files: + if os.path.isfile(os.path.join(assets_folder, f)) is False: + if ( + download_file(os.path.join("assets", f), + os.path.join(assets_folder, f)) + is True + ): + logger.info(f"Downloaded {f}") + + +def check_assets(): + required_files = [ + "banner.png", + "charts.html", + "script.js", + "style.css", + "dark-theme.css", + ] + assets_folder = os.path.join(Path().absolute(), "assets") + if os.path.isdir(assets_folder) is False: + logger.info(f"Assets folder not found at {assets_folder}") + download_assets(assets_folder, required_files) + else: + for f in required_files: + if os.path.isfile(os.path.join(assets_folder, f)) is False: + logger.info(f"Missing file {f} in {assets_folder}") + download_assets(assets_folder, required_files) + break + +last_sent_log_index = 0 + +class AnalyticsServer(Thread): + def __init__( + self, + host: str = "127.0.0.1", + port: int = 5000, + refresh: int = 5, + days_ago: int = 7, + username: str = None + ): + super(AnalyticsServer, self).__init__() + + check_assets() + + self.host = host + self.port = port + self.refresh = refresh + self.days_ago = days_ago + self.username = username + + def generate_log(): + global last_sent_log_index # Use the global variable + + # Get the last received log index from the client request parameters + last_received_index = int(request.args.get("lastIndex", last_sent_log_index)) + + logs_path = os.path.join(Path().absolute(), "logs") + log_file_path = os.path.join(logs_path, f"{username}.log") + try: + with open(log_file_path, "r") as log_file: + log_content = log_file.read() + + # Extract new log entries since the last received index + new_log_entries = log_content[last_received_index:] + last_sent_log_index = len(log_content) # Update the last sent index + + return Response(new_log_entries, status=200, mimetype="text/plain") + + except FileNotFoundError: + return Response("Log file not found.", status=404, mimetype="text/plain") + + self.app = Flask( + __name__, + template_folder=os.path.join(Path().absolute(), "assets"), + static_folder=os.path.join(Path().absolute(), "assets"), + ) + self.app.add_url_rule( + "/", + "index", + index, + defaults={"refresh": refresh, "days_ago": days_ago}, + methods=["GET"], + ) + self.app.add_url_rule("/streamers", "streamers", + streamers, methods=["GET"]) + self.app.add_url_rule( + "/json/", "json", read_json, methods=["GET"] + ) + self.app.add_url_rule("/json_all", "json_all", + json_all, methods=["GET"]) + self.app.add_url_rule( + "/log", "log", generate_log, methods=["GET"]) + + def run(self): + logger.info( + f"Analytics running on http://{self.host}:{self.port}/", + extra={"emoji": ":globe_with_meridians:"}, + ) + self.app.run(host=self.host, port=self.port, + threaded=True, debug=False) diff --git a/TwitchChannelPointsMiner/classes/Chat.py b/TwitchChannelPointsMiner/classes/Chat.py new file mode 100644 index 0000000..ef58d8e --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Chat.py @@ -0,0 +1,105 @@ +import logging +import time +from enum import Enum, auto +from threading import Thread + +from irc.bot import SingleServerIRCBot + +from TwitchChannelPointsMiner.constants import IRC, IRC_PORT +from TwitchChannelPointsMiner.classes.Settings import Events, Settings + +logger = logging.getLogger(__name__) + + +class ChatPresence(Enum): + ALWAYS = auto() + NEVER = auto() + ONLINE = auto() + OFFLINE = auto() + + def __str__(self): + return self.name + + +class ClientIRC(SingleServerIRCBot): + def __init__(self, username, token, channel): + self.token = token + self.channel = "#" + channel + self.__active = False + + super(ClientIRC, self).__init__( + [(IRC, IRC_PORT, f"oauth:{token}")], username, username + ) + + def on_welcome(self, client, event): + client.join(self.channel) + + def start(self): + self.__active = True + self._connect() + while self.__active: + try: + self.reactor.process_once(timeout=0.2) + time.sleep(0.01) + except Exception as e: + logger.error( + f"Exception raised: {e}. Thread is active: {self.__active}" + ) + + def die(self, msg="Bye, cruel world!"): + self.connection.disconnect(msg) + self.__active = False + + """ + def on_join(self, connection, event): + logger.info(f"Event: {event}", extra={"emoji": ":speech_balloon:"}) + """ + + # """ + def on_pubmsg(self, connection, event): + msg = event.arguments[0] + mention = None + + if Settings.disable_at_in_nickname is True: + mention = f"{self._nickname.lower()}" + else: + mention = f"@{self._nickname.lower()}" + + # also self._realname + # if msg.startswith(f"@{self._nickname}"): + if mention != None and mention in msg.lower(): + # nickname!username@nickname.tmi.twitch.tv + nick = event.source.split("!", 1)[0] + # chan = event.target + + logger.info(f"{nick} at {self.channel} wrote: {msg}", extra={ + "emoji": ":speech_balloon:", "event": Events.CHAT_MENTION}) + # """ + + +class ThreadChat(Thread): + def __deepcopy__(self, memo): + return None + + def __init__(self, username, token, channel): + super(ThreadChat, self).__init__() + + self.username = username + self.token = token + self.channel = channel + + self.chat_irc = None + + def run(self): + self.chat_irc = ClientIRC(self.username, self.token, self.channel) + logger.info( + f"Join IRC Chat: {self.channel}", extra={"emoji": ":speech_balloon:"} + ) + self.chat_irc.start() + + def stop(self): + if self.chat_irc is not None: + logger.info( + f"Leave IRC Chat: {self.channel}", extra={"emoji": ":speech_balloon:"} + ) + self.chat_irc.die() diff --git a/TwitchChannelPointsMiner/classes/Discord.py b/TwitchChannelPointsMiner/classes/Discord.py new file mode 100644 index 0000000..00b9670 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Discord.py @@ -0,0 +1,24 @@ +from textwrap import dedent + +import requests + +from TwitchChannelPointsMiner.classes.Settings import Events + + +class Discord(object): + __slots__ = ["webhook_api", "events"] + + def __init__(self, webhook_api: str, events: list): + self.webhook_api = webhook_api + self.events = [str(e) for e in events] + + def send(self, message: str, event: Events) -> None: + if str(event) in self.events: + requests.post( + url=self.webhook_api, + data={ + "content": dedent(message), + "username": "Twitch Channel Points Miner", + "avatar_url": "https://i.imgur.com/X9fEkhT.png", + }, + ) diff --git a/TwitchChannelPointsMiner/classes/Exceptions.py b/TwitchChannelPointsMiner/classes/Exceptions.py new file mode 100644 index 0000000..9f88b60 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Exceptions.py @@ -0,0 +1,14 @@ +class StreamerDoesNotExistException(Exception): + pass + + +class StreamerIsOfflineException(Exception): + pass + + +class WrongCookiesException(Exception): + pass + + +class BadCredentialsException(Exception): + pass diff --git a/TwitchChannelPointsMiner/classes/Matrix.py b/TwitchChannelPointsMiner/classes/Matrix.py new file mode 100644 index 0000000..6cfa9ae --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Matrix.py @@ -0,0 +1,40 @@ +from textwrap import dedent + +import logging +import requests +from urllib.parse import quote + +from TwitchChannelPointsMiner.classes.Settings import Events + + +class Matrix(object): + __slots__ = ["access_token", "homeserver", "room_id", "events"] + + def __init__(self, username: str, password: str, homeserver: str, room_id: str, events: list): + self.homeserver = homeserver + self.room_id = quote(room_id) + self.events = [str(e) for e in events] + + body = requests.post( + url=f"https://{self.homeserver}/_matrix/client/r0/login", + json={ + "user": username, + "password": password, + "type": "m.login.password" + } + ).json() + + self.access_token = body.get("access_token") + + if not self.access_token: + logging.getLogger(__name__).info("Invalid Matrix password provided. Notifications will not be sent.") + + def send(self, message: str, event: Events) -> None: + if str(event) in self.events: + requests.post( + url=f"https://{self.homeserver}/_matrix/client/r0/rooms/{self.room_id}/send/m.room.message?access_token={self.access_token}", + json={ + "body": dedent(message), + "msgtype": "m.text" + } + ) diff --git a/TwitchChannelPointsMiner/classes/Pushover.py b/TwitchChannelPointsMiner/classes/Pushover.py new file mode 100644 index 0000000..3d5a3c8 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Pushover.py @@ -0,0 +1,30 @@ +from textwrap import dedent + +import requests + +from TwitchChannelPointsMiner.classes.Settings import Events + + +class Pushover(object): + __slots__ = ["userkey", "token", "priority", "sound", "events"] + + def __init__(self, userkey: str, token: str, priority, sound, events: list): + self.userkey = userkey + self.token = token + self. priority = priority + self.sound = sound + self.events = [str(e) for e in events] + + def send(self, message: str, event: Events) -> None: + if str(event) in self.events: + requests.post( + url="https://api.pushover.net/1/messages.json", + data={ + "user": self.userkey, + "token": self.token, + "message": dedent(message), + "title": "Twitch Channel Points Miner", + "priority": self.priority, + "sound": self.sound, + }, + ) diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py new file mode 100644 index 0000000..3db6b62 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -0,0 +1,53 @@ +from enum import Enum, auto + + +class Priority(Enum): + ORDER = auto() + STREAK = auto() + DROPS = auto() + SUBSCRIBED = auto() + POINTS_ASCENDING = auto() + POINTS_DESCEDING = auto() + + +class FollowersOrder(Enum): + ASC = auto() + DESC = auto() + + def __str__(self): + return self.name + + +# Empty object shared between class +class Settings(object): + __slots__ = ["logger", "streamer_settings", + "enable_analytics", "disable_ssl_cert_verification", "disable_at_in_nickname"] + + +class Events(Enum): + STREAMER_ONLINE = auto() + STREAMER_OFFLINE = auto() + GAIN_FOR_RAID = auto() + GAIN_FOR_CLAIM = auto() + GAIN_FOR_WATCH = auto() + GAIN_FOR_WATCH_STREAK = auto() + BET_WIN = auto() + BET_LOSE = auto() + BET_REFUND = auto() + BET_FILTERS = auto() + BET_GENERAL = auto() + BET_FAILED = auto() + BET_START = auto() + BONUS_CLAIM = auto() + MOMENT_CLAIM = auto() + JOIN_RAID = auto() + DROP_CLAIM = auto() + DROP_STATUS = auto() + CHAT_MENTION = auto() + + def __str__(self): + return self.name + + @classmethod + def get(cls, key): + return getattr(cls, str(key)) if str(key) in dir(cls) else None diff --git a/TwitchChannelPointsMiner/classes/Telegram.py b/TwitchChannelPointsMiner/classes/Telegram.py new file mode 100644 index 0000000..c6d9055 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Telegram.py @@ -0,0 +1,29 @@ +from textwrap import dedent + +import requests + +from TwitchChannelPointsMiner.classes.Settings import Events + + +class Telegram(object): + __slots__ = ["chat_id", "telegram_api", "events", "disable_notification"] + + def __init__( + self, chat_id: int, token: str, events: list, disable_notification: bool = False + ): + self.chat_id = chat_id + self.telegram_api = f"https://api.telegram.org/bot{token}/sendMessage" + self.events = [str(e) for e in events] + self.disable_notification = disable_notification + + def send(self, message: str, event: Events) -> None: + if str(event) in self.events: + requests.post( + url=self.telegram_api, + data={ + "chat_id": self.chat_id, + "text": dedent(message), + "disable_web_page_preview": True, # include link to twitch streamer? + "disable_notification": self.disable_notification, # no sound, notif just in tray + }, + ) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py new file mode 100644 index 0000000..7a9dd47 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -0,0 +1,859 @@ +# For documentation on Twitch GraphQL API see: +# https://www.apollographql.com/docs/ +# https://github.com/mauricew/twitch-graphql-api +# Full list of available methods: https://azr.ivr.fi/schema/query.doc.html (a bit outdated) + + +import copy +import logging +import os +import random +import re +import string +import time +# from datetime import datetime +from pathlib import Path +from secrets import choice, token_hex + +# import json +# from base64 import urlsafe_b64decode + +import requests + +from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign +from TwitchChannelPointsMiner.classes.entities.Drop import Drop +from TwitchChannelPointsMiner.classes.Exceptions import ( + StreamerDoesNotExistException, + StreamerIsOfflineException, +) +from TwitchChannelPointsMiner.classes.Settings import ( + Events, + FollowersOrder, + Priority, + Settings, +) +from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin +from TwitchChannelPointsMiner.constants import ( + CLIENT_ID, + CLIENT_VERSION, + URL, + GQLOperations, +) +from TwitchChannelPointsMiner.utils import ( + _millify, + create_chunks, + internet_connection_available, +) + +logger = logging.getLogger(__name__) + + +class Twitch(object): + __slots__ = [ + "cookies_file", + "user_agent", + "twitch_login", + "running", + "device_id", + # "integrity", + # "integrity_expire", + "client_session", + "client_version", + "twilight_build_id_pattern", + ] + + def __init__(self, username, user_agent, password=None): + cookies_path = os.path.join(Path().absolute(), "cookies") + Path(cookies_path).mkdir(parents=True, exist_ok=True) + self.cookies_file = os.path.join(cookies_path, f"{username}.pkl") + self.user_agent = user_agent + self.device_id = "".join( + choice(string.ascii_letters + string.digits) for _ in range(32) + ) + self.twitch_login = TwitchLogin( + CLIENT_ID, self.device_id, username, self.user_agent, password=password + ) + self.running = True + # self.integrity = None + # self.integrity_expire = 0 + self.client_session = token_hex(16) + self.client_version = CLIENT_VERSION + self.twilight_build_id_pattern = re.compile( + r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" + ) + + def login(self): + if not os.path.isfile(self.cookies_file): + if self.twitch_login.login_flow(): + self.twitch_login.save_cookies(self.cookies_file) + else: + self.twitch_login.load_cookies(self.cookies_file) + self.twitch_login.set_token(self.twitch_login.get_auth_token()) + + # === STREAMER / STREAM / INFO === # + def update_stream(self, streamer): + if streamer.stream.update_required() is True: + stream_info = self.get_stream_info(streamer) + if stream_info is not None: + streamer.stream.update( + broadcast_id=stream_info["stream"]["id"], + title=stream_info["broadcastSettings"]["title"], + game=stream_info["broadcastSettings"]["game"], + tags=stream_info["stream"]["tags"], + viewers_count=stream_info["stream"]["viewersCount"], + ) + + event_properties = { + "channel_id": streamer.channel_id, + "broadcast_id": streamer.stream.broadcast_id, + "player": "site", + "user_id": self.twitch_login.get_user_id(), + "live": True, + "channel": streamer.username + } + + if ( + streamer.stream.game_name() is not None + and streamer.stream.game_id() is not None + and streamer.settings.claim_drops is True + ): + event_properties["game"] = streamer.stream.game_name() + event_properties["game_id"] = streamer.stream.game_id() + # Update also the campaigns_ids so we are sure to tracking the correct campaign + streamer.stream.campaigns_ids = ( + self.__get_campaign_ids_from_streamer(streamer) + ) + + streamer.stream.payload = [ + {"event": "minute-watched", "properties": event_properties} + ] + + def get_spade_url(self, streamer): + try: + # fixes AttributeError: 'NoneType' object has no attribute 'group' + # headers = {"User-Agent": self.user_agent} + from TwitchChannelPointsMiner.constants import USER_AGENTS + headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + + main_page_request = requests.get( + streamer.streamer_url, headers=headers) + response = main_page_request.text + # logger.info(response) + regex_settings = "(https://static.twitchcdn.net/config/settings.*?js)" + settings_url = re.search(regex_settings, response).group(1) + + settings_request = requests.get(settings_url, headers=headers) + response = settings_request.text + regex_spade = '"spade_url":"(.*?)"' + streamer.stream.spade_url = re.search( + regex_spade, response).group(1) + except requests.exceptions.RequestException as e: + logger.error( + f"Something went wrong during extraction of 'spade_url': {e}") + + def get_broadcast_id(self, streamer): + json_data = copy.deepcopy(GQLOperations.WithIsStreamLiveQuery) + json_data["variables"] = {"id": streamer.channel_id} + response = self.post_gql_request(json_data) + if response != {}: + stream = response["data"]["user"]["stream"] + if stream is not None: + return stream["id"] + else: + raise StreamerIsOfflineException + + def get_stream_info(self, streamer): + json_data = copy.deepcopy( + GQLOperations.VideoPlayerStreamInfoOverlayChannel) + json_data["variables"] = {"channel": streamer.username} + response = self.post_gql_request(json_data) + if response != {}: + if response["data"]["user"]["stream"] is None: + raise StreamerIsOfflineException + else: + return response["data"]["user"] + + def check_streamer_online(self, streamer): + if time.time() < streamer.offline_at + 60: + return + + if streamer.is_online is False: + try: + self.get_spade_url(streamer) + self.update_stream(streamer) + except StreamerIsOfflineException: + streamer.set_offline() + else: + streamer.set_online() + else: + try: + self.update_stream(streamer) + except StreamerIsOfflineException: + streamer.set_offline() + + def get_channel_id(self, streamer_username): + json_data = copy.deepcopy(GQLOperations.ReportMenuItem) + json_data["variables"] = {"channelLogin": streamer_username} + json_response = self.post_gql_request(json_data) + if ( + "data" not in json_response + or "user" not in json_response["data"] + or json_response["data"]["user"] is None + ): + raise StreamerDoesNotExistException + else: + return json_response["data"]["user"]["id"] + + def get_followers( + self, limit: int = 100, order: FollowersOrder = FollowersOrder.ASC + ): + json_data = copy.deepcopy(GQLOperations.ChannelFollows) + json_data["variables"] = {"limit": limit, "order": str(order)} + has_next = True + last_cursor = "" + follows = [] + while has_next is True: + json_data["variables"]["cursor"] = last_cursor + json_response = self.post_gql_request(json_data) + try: + follows_response = json_response["data"]["user"]["follows"] + last_cursor = None + for f in follows_response["edges"]: + follows.append(f["node"]["login"].lower()) + last_cursor = f["cursor"] + + has_next = follows_response["pageInfo"]["hasNextPage"] + except KeyError: + return [] + return follows + + def update_raid(self, streamer, raid): + if streamer.raid != raid: + streamer.raid = raid + json_data = copy.deepcopy(GQLOperations.JoinRaid) + json_data["variables"] = {"input": {"raidID": raid.raid_id}} + self.post_gql_request(json_data) + + logger.info( + f"Joining raid from {streamer} to {raid.target_login}!", + extra={"emoji": ":performing_arts:", + "event": Events.JOIN_RAID}, + ) + + def viewer_is_mod(self, streamer): + json_data = copy.deepcopy(GQLOperations.ModViewChannelQuery) + json_data["variables"] = {"channelLogin": streamer.username} + response = self.post_gql_request(json_data) + try: + streamer.viewer_is_mod = response["data"]["user"]["self"]["isModerator"] + except (ValueError, KeyError): + streamer.viewer_is_mod = False + + # === 'GLOBALS' METHODS === # + # Create chunk of sleep of speed-up the break loop after CTRL+C + def __chuncked_sleep(self, seconds, chunk_size=3): + sleep_time = max(seconds, 0) / chunk_size + for i in range(0, chunk_size): + time.sleep(sleep_time) + if self.running is False: + break + + def __check_connection_handler(self, chunk_size): + # The success rate It's very hight usually. Why we have failed? + # Check internet connection ... + while internet_connection_available() is False: + random_sleep = random.randint(1, 3) + logger.warning( + f"No internet connection available! Retry after {random_sleep}m" + ) + self.__chuncked_sleep(random_sleep * 60, chunk_size=chunk_size) + + def post_gql_request(self, json_data): + try: + response = requests.post( + GQLOperations.url, + json=json_data, + headers={ + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + # "Client-Integrity": self.post_integrity(), + "Client-Session-Id": self.client_session, + "Client-Version": self.update_client_version(), + "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, + }, + ) + logger.debug( + f"Data: {json_data}, Status code: {response.status_code}, Content: {response.text}" + ) + return response.json() + except requests.exceptions.RequestException as e: + logger.error( + f"Error with GQLOperations ({json_data['operationName']}): {e}" + ) + return {} + + # Request for Integrity Token + # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests + # Regenerate Integrity Token 5 minutes before expire + """def post_integrity(self): + if ( + self.integrity_expire - datetime.now().timestamp() * 1000 > 5 * 60 * 1000 + and self.integrity is not None + ): + return self.integrity + try: + response = requests.post( + GQLOperations.integrity_url, + json={}, + headers={ + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + "Client-Session-Id": self.client_session, + "Client-Version": self.update_client_version(), + "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, + }, + ) + logger.debug( + f"Data: [], Status code: {response.status_code}, Content: {response.text}" + ) + self.integrity = response.json().get("token", None) + # logger.info(f"integrity: {self.integrity}") + + if self.isBadBot(self.integrity) is True: + logger.info( + "Uh-oh, Twitch has detected this miner as a \"Bad Bot\". Don't worry.") + + self.integrity_expire = response.json().get("expiration", 0) + # logger.info(f"integrity_expire: {self.integrity_expire}") + return self.integrity + except requests.exceptions.RequestException as e: + logger.error(f"Error with post_integrity: {e}") + return self.integrity + + # verify the integrity token's contents for the "is_bad_bot" flag + def isBadBot(self, integrity): + stripped_token: str = self.integrity.split('.')[2] + "==" + messy_json: str = urlsafe_b64decode( + stripped_token.encode()).decode(errors="ignore") + match = re.search(r'(.+)(?<="}).+$', messy_json) + if match is None: + # raise MinerException("Unable to parse the integrity token") + logger.info("Unable to parse the integrity token. Don't worry.") + return + decoded_header = json.loads(match.group(1)) + # logger.info(f"decoded_header: {decoded_header}") + if decoded_header.get("is_bad_bot", "false") != "false": + return True + else: + return False""" + + def update_client_version(self): + try: + response = requests.get(URL) + if response.status_code != 200: + logger.debug( + f"Error with update_client_version: {response.status_code}" + ) + return self.client_version + matcher = re.search(self.twilight_build_id_pattern, response.text) + if not matcher: + logger.debug("Error with update_client_version: no match") + return self.client_version + self.client_version = matcher.group(1) + logger.debug(f"Client version: {self.client_version}") + return self.client_version + except requests.exceptions.RequestException as e: + logger.error(f"Error with update_client_version: {e}") + return self.client_version + + def send_minute_watched_events(self, streamers, priority, chunk_size=3): + while self.running: + try: + streamers_index = [ + i + for i in range(0, len(streamers)) + if streamers[i].is_online is True + and ( + streamers[i].online_at == 0 + or (time.time() - streamers[i].online_at) > 30 + ) + ] + + for index in streamers_index: + if (streamers[index].stream.update_elapsed() / 60) > 10: + # Why this user It's currently online but the last updated was more than 10minutes ago? + # Please perform a manually update and check if the user it's online + self.check_streamer_online(streamers[index]) + + streamers_watching = [] + for prior in priority: + if prior == Priority.ORDER and len(streamers_watching) < 2: + # Get the first 2 items, they are already in order + streamers_watching += streamers_index[:2] + + elif ( + prior in [Priority.POINTS_ASCENDING, + Priority.POINTS_DESCEDING] + and len(streamers_watching) < 2 + ): + items = [ + {"points": streamers[index].channel_points, + "index": index} + for index in streamers_index + ] + items = sorted( + items, + key=lambda x: x["points"], + reverse=( + True if prior == Priority.POINTS_DESCEDING else False + ), + ) + streamers_watching += [item["index"] + for item in items][:2] + + elif prior == Priority.STREAK and len(streamers_watching) < 2: + """ + Check if we need need to change priority based on watch streak + Viewers receive points for returning for x consecutive streams. + Each stream must be at least 10 minutes long and it must have been at least 30 minutes since the last stream ended. + Watch at least 6m for get the +10 + """ + for index in streamers_index: + if ( + streamers[index].settings.watch_streak is True + and streamers[index].stream.watch_streak_missing is True + and ( + streamers[index].offline_at == 0 + or ( + (time.time() - + streamers[index].offline_at) + // 60 + ) + > 30 + ) + and streamers[index].stream.minute_watched < 7 # fix #425 + ): + streamers_watching.append(index) + if len(streamers_watching) == 2: + break + + elif prior == Priority.DROPS and len(streamers_watching) < 2: + for index in streamers_index: + if streamers[index].drops_condition() is True: + streamers_watching.append(index) + if len(streamers_watching) == 2: + break + + elif prior == Priority.SUBSCRIBED and len(streamers_watching) < 2: + streamers_with_multiplier = [ + index + for index in streamers_index + if streamers[index].viewer_has_points_multiplier() + ] + streamers_with_multiplier = sorted( + streamers_with_multiplier, + key=lambda x: streamers[x].total_points_multiplier( + ), + reverse=True, + ) + streamers_watching += streamers_with_multiplier[:2] + + """ + Twitch has a limit - you can't watch more than 2 channels at one time. + We take the first two streamers from the list as they have the highest priority (based on order or WatchStreak). + """ + streamers_watching = streamers_watching[:2] + + for index in streamers_watching: + next_iteration = time.time() + 60 / len(streamers_watching) + + try: + response = requests.post( + streamers[index].stream.spade_url, + data=streamers[index].stream.encode_payload(), + headers={"User-Agent": self.user_agent}, + timeout=60, + ) + logger.debug( + f"Send minute watched request for {streamers[index]} - Status code: {response.status_code}" + ) + if response.status_code == 204: + streamers[index].stream.update_minute_watched() + + """ + Remember, you can only earn progress towards a time-based Drop on one participating channel at a time. [ ! ! ! ] + You can also check your progress towards Drops within a campaign anytime by viewing the Drops Inventory. + For time-based Drops, if you are unable to claim the Drop in time, you will be able to claim it from the inventory page until the Drops campaign ends. + """ + + for campaign in streamers[index].stream.campaigns: + for drop in campaign.drops: + # We could add .has_preconditions_met condition inside is_printable + if ( + drop.has_preconditions_met is not False + and drop.is_printable is True + ): + drop_messages = [ + f"{streamers[index]} is streaming {streamers[index].stream}", + f"Campaign: {campaign}", + f"Drop: {drop}", + f"{drop.progress_bar()}", + ] + for single_line in drop_messages: + logger.info( + single_line, + extra={ + "event": Events.DROP_STATUS, + "skip_telegram": True, + "skip_discord": True, + "skip_webhook": True, + "skip_matrix": True, + }, + ) + + if Settings.logger.telegram is not None: + Settings.logger.telegram.send( + "\n".join(drop_messages), + Events.DROP_STATUS, + ) + + if Settings.logger.discord is not None: + Settings.logger.discord.send( + "\n".join(drop_messages), + Events.DROP_STATUS, + ) + if Settings.logger.webhook is not None: + Settings.logger.webhook.send( + "\n".join(drop_messages), + Events.DROP_STATUS, + ) + + except requests.exceptions.ConnectionError as e: + logger.error( + f"Error while trying to send minute watched: {e}") + self.__check_connection_handler(chunk_size) + except requests.exceptions.Timeout as e: + logger.error( + f"Error while trying to send minute watched: {e}") + + self.__chuncked_sleep( + next_iteration - time.time(), chunk_size=chunk_size + ) + + if streamers_watching == []: + self.__chuncked_sleep(60, chunk_size=chunk_size) + except Exception: + logger.error( + "Exception raised in send minute watched", exc_info=True) + + # === CHANNEL POINTS / PREDICTION === # + # Load the amount of current points for a channel, check if a bonus is available + def load_channel_points_context(self, streamer): + json_data = copy.deepcopy(GQLOperations.ChannelPointsContext) + json_data["variables"] = {"channelLogin": streamer.username} + + response = self.post_gql_request(json_data) + if response != {}: + if response["data"]["community"] is None: + raise StreamerDoesNotExistException + channel = response["data"]["community"]["channel"] + community_points = channel["self"]["communityPoints"] + streamer.channel_points = community_points["balance"] + streamer.activeMultipliers = community_points["activeMultipliers"] + + if community_points["availableClaim"] is not None: + self.claim_bonus( + streamer, community_points["availableClaim"]["id"]) + + def make_predictions(self, event): + decision = event.bet.calculate(event.streamer.channel_points) + # selector_index = 0 if decision["choice"] == "A" else 1 + + logger.info( + f"Going to complete bet for {event}", + extra={ + "emoji": ":four_leaf_clover:", + "event": Events.BET_GENERAL, + }, + ) + if event.status == "ACTIVE": + skip, compared_value = event.bet.skip() + if skip is True: + logger.info( + f"Skip betting for the event {event}", + extra={ + "emoji": ":pushpin:", + "event": Events.BET_FILTERS, + }, + ) + logger.info( + f"Skip settings {event.bet.settings.filter_condition}, current value is: {compared_value}", + extra={ + "emoji": ":pushpin:", + "event": Events.BET_FILTERS, + }, + ) + else: + if decision["amount"] >= 10: + logger.info( + # f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(decision['choice'])}", + extra={ + "emoji": ":four_leaf_clover:", + "event": Events.BET_GENERAL, + }, + ) + + json_data = copy.deepcopy(GQLOperations.MakePrediction) + json_data["variables"] = { + "input": { + "eventID": event.event_id, + "outcomeID": decision["id"], + "points": decision["amount"], + "transactionID": token_hex(16), + } + } + response = self.post_gql_request(json_data) + if ( + "data" in response + and "makePrediction" in response["data"] + and "error" in response["data"]["makePrediction"] + and response["data"]["makePrediction"]["error"] is not None + ): + error_code = response["data"]["makePrediction"]["error"]["code"] + logger.error( + f"Failed to place bet, error: {error_code}", + extra={ + "emoji": ":four_leaf_clover:", + "event": Events.BET_FAILED, + }, + ) + else: + logger.info( + f"Bet won't be placed as the amount {_millify(decision['amount'])} is less than the minimum required 10", + extra={ + "emoji": ":four_leaf_clover:", + "event": Events.BET_GENERAL, + }, + ) + else: + logger.info( + f"Oh no! The event is not active anymore! Current status: {event.status}", + extra={ + "emoji": ":disappointed_relieved:", + "event": Events.BET_FAILED, + }, + ) + + def claim_bonus(self, streamer, claim_id): + if Settings.logger.less is False: + logger.info( + f"Claiming the bonus for {streamer}!", + extra={"emoji": ":gift:", "event": Events.BONUS_CLAIM}, + ) + + json_data = copy.deepcopy(GQLOperations.ClaimCommunityPoints) + json_data["variables"] = { + "input": {"channelID": streamer.channel_id, "claimID": claim_id} + } + self.post_gql_request(json_data) + + # === MOMENTS === # + def claim_moment(self, streamer, moment_id): + if Settings.logger.less is False: + logger.info( + f"Claiming the moment for {streamer}!", + extra={"emoji": ":video_camera:", + "event": Events.MOMENT_CLAIM}, + ) + + json_data = copy.deepcopy(GQLOperations.CommunityMomentCallout_Claim) + json_data["variables"] = { + "input": {"momentID": moment_id} + } + self.post_gql_request(json_data) + + # === CAMPAIGNS / DROPS / INVENTORY === # + def __get_campaign_ids_from_streamer(self, streamer): + json_data = copy.deepcopy( + GQLOperations.DropsHighlightService_AvailableDrops) + json_data["variables"] = {"channelID": streamer.channel_id} + response = self.post_gql_request(json_data) + try: + return ( + [] + if response["data"]["channel"]["viewerDropCampaigns"] is None + else [ + item["id"] + for item in response["data"]["channel"]["viewerDropCampaigns"] + ] + ) + except (ValueError, KeyError): + return [] + + def __get_inventory(self): + response = self.post_gql_request(GQLOperations.Inventory) + try: + return ( + response["data"]["currentUser"]["inventory"] if response != {} else {} + ) + except (ValueError, KeyError, TypeError): + return {} + + def __get_drops_dashboard(self, status=None): + response = self.post_gql_request(GQLOperations.ViewerDropsDashboard) + campaigns = response["data"]["currentUser"]["dropCampaigns"] or [] + + if status is not None: + campaigns = list( + filter(lambda x: x["status"] == status.upper(), campaigns)) or [] + + return campaigns + + def __get_campaigns_details(self, campaigns): + result = [] + chunks = create_chunks(campaigns, 20) + for chunk in chunks: + json_data = [] + for campaign in chunk: + json_data.append(copy.deepcopy( + GQLOperations.DropCampaignDetails)) + json_data[-1]["variables"] = { + "dropID": campaign["id"], + "channelLogin": f"{self.twitch_login.get_user_id()}", + } + + response = self.post_gql_request(json_data) + for r in response: + if r["data"]["user"] is not None: + result.append(r["data"]["user"]["dropCampaign"]) + return result + + def __sync_campaigns(self, campaigns): + # We need the inventory only for get the real updated value/progress + # Get data from inventory and sync current status with streamers.campaigns + inventory = self.__get_inventory() + if inventory not in [None, {}] and inventory["dropCampaignsInProgress"] not in [ + None, + {}, + ]: + # Iterate all campaigns from dashboard (only active, with working drops) + # In this array we have also the campaigns never started from us (not in nventory) + for i in range(len(campaigns)): + campaigns[i].clear_drops() # Remove all the claimed drops + # Iterate all campaigns currently in progress from out inventory + for progress in inventory["dropCampaignsInProgress"]: + if progress["id"] == campaigns[i].id: + campaigns[i].in_inventory = True + campaigns[i].sync_drops( + progress["timeBasedDrops"], self.claim_drop + ) + # Remove all the claimed drops + campaigns[i].clear_drops() + break + return campaigns + + def claim_drop(self, drop): + logger.info( + f"Claim {drop}", extra={"emoji": ":package:", "event": Events.DROP_CLAIM} + ) + + json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) + json_data["variables"] = { + "input": {"dropInstanceID": drop.drop_instance_id}} + response = self.post_gql_request(json_data) + try: + # response["data"]["claimDropRewards"] can be null and respose["data"]["errors"] != [] + # or response["data"]["claimDropRewards"]["status"] === DROP_INSTANCE_ALREADY_CLAIMED + if ("claimDropRewards" in response["data"]) and ( + response["data"]["claimDropRewards"] is None + ): + return False + elif ("errors" in response["data"]) and (response["data"]["errors"] != []): + return False + elif ("claimDropRewards" in response["data"]) and ( + response["data"]["claimDropRewards"]["status"] + in ["ELIGIBLE_FOR_ALL", "DROP_INSTANCE_ALREADY_CLAIMED"] + ): + return True + else: + return False + except (ValueError, KeyError): + return False + + def claim_all_drops_from_inventory(self): + inventory = self.__get_inventory() + if inventory not in [None, {}]: + if inventory["dropCampaignsInProgress"] not in [None, {}]: + for campaign in inventory["dropCampaignsInProgress"]: + for drop_dict in campaign["timeBasedDrops"]: + drop = Drop(drop_dict) + drop.update(drop_dict["self"]) + if drop.is_claimable is True: + drop.is_claimed = self.claim_drop(drop) + time.sleep(random.uniform(5, 10)) + + def sync_campaigns(self, streamers, chunk_size=3): + campaigns_update = 0 + while self.running: + try: + # Get update from dashboard each 60minutes + if ( + campaigns_update == 0 + # or ((time.time() - campaigns_update) / 60) > 60 + + # TEMPORARY AUTO DROP CLAIMING FIX + # 30 minutes instead of 60 minutes + or ((time.time() - campaigns_update) / 30) > 30 + ##################################### + ): + campaigns_update = time.time() + + # TEMPORARY AUTO DROP CLAIMING FIX + self.claim_all_drops_from_inventory() + ##################################### + + # Get full details from current ACTIVE campaigns + # Use dashboard so we can explore new drops not currently active in our Inventory + campaigns_details = self.__get_campaigns_details( + self.__get_drops_dashboard(status="ACTIVE") + ) + campaigns = [] + + # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet + for index in range(0, len(campaigns_details)): + if campaigns_details[index] is not None: + campaign = Campaign(campaigns_details[index]) + if campaign.dt_match is True: + # Remove all the drops already claimed or with dt not matching + campaign.clear_drops() + if campaign.drops != []: + campaigns.append(campaign) + else: + continue + + # Divide et impera :) + campaigns = self.__sync_campaigns(campaigns) + + # Check if user It's currently streaming the same game present in campaigns_details + for i in range(0, len(streamers)): + if streamers[i].drops_condition() is True: + # yes! The streamer[i] have the drops_tags enabled and we It's currently stream a game with campaign active! + # With 'campaigns_ids' we are also sure that this streamer have the campaign active. + # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! + streamers[i].stream.campaigns = list( + filter( + lambda x: x.drops != [] + and x.game == streamers[i].stream.game + and x.id in streamers[i].stream.campaigns_ids, + campaigns, + ) + ) + + except (ValueError, KeyError, requests.exceptions.ConnectionError) as e: + logger.error(f"Error while syncing inventory: {e}") + self.__check_connection_handler(chunk_size) + + self.__chuncked_sleep(60, chunk_size=chunk_size) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py new file mode 100644 index 0000000..d8784ad --- /dev/null +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -0,0 +1,360 @@ +# Based on https://github.com/derrod/twl.py +# Original Copyright (c) 2020 Rodney +# The MIT License (MIT) + +import copy +# import getpass +import logging +import os +import pickle + +# import webbrowser +# import browser_cookie3 + +import requests + +from TwitchChannelPointsMiner.classes.Exceptions import ( + BadCredentialsException, + WrongCookiesException, +) +from TwitchChannelPointsMiner.constants import CLIENT_ID, GQLOperations, USER_AGENTS + +from datetime import datetime, timedelta, timezone +from time import sleep + +logger = logging.getLogger(__name__) + +"""def interceptor(request) -> str: + if ( + request.method == 'POST' + and request.url == 'https://passport.twitch.tv/protected_login' + ): + import json + body = request.body.decode('utf-8') + data = json.loads(body) + data['client_id'] = CLIENT_ID + request.body = json.dumps(data).encode('utf-8') + del request.headers['Content-Length'] + request.headers['Content-Length'] = str(len(request.body))""" + + +class TwitchLogin(object): + __slots__ = [ + "client_id", + "device_id", + "token", + "login_check_result", + "session", + "session", + "username", + "password", + "user_id", + "email", + "cookies", + "shared_cookies" + ] + + def __init__(self, client_id, device_id, username, user_agent, password=None): + self.client_id = client_id + self.device_id = device_id + self.token = None + self.login_check_result = False + self.session = requests.session() + self.session.headers.update( + {"Client-ID": self.client_id, + "X-Device-Id": self.device_id, "User-Agent": user_agent} + ) + self.username = username + self.password = password + self.user_id = None + self.email = None + + self.cookies = [] + self.shared_cookies = [] + + def login_flow(self): + logger.info("You'll have to login to Twitch!") + + post_data = { + "client_id": self.client_id, + "scopes": ( + "channel_read chat:read user_blocks_edit " + "user_blocks_read user_follows_edit user_read" + ) + } + # login-fix + use_backup_flow = False + # use_backup_flow = True + while True: + logger.info("Trying the TV login method..") + + login_response = self.send_oauth_request( + "https://id.twitch.tv/oauth2/device", post_data) + + # { + # "device_code": "40 chars [A-Za-z0-9]", + # "expires_in": 1800, + # "interval": 5, + # "user_code": "8 chars [A-Z]", + # "verification_uri": "https://www.twitch.tv/activate" + # } + + if login_response.status_code != 200: + logger.error("TV login response is not 200. Try again") + break + + login_response_json = login_response.json() + + if "user_code" in login_response_json: + user_code: str = login_response_json["user_code"] + now = datetime.now(timezone.utc) + device_code: str = login_response_json["device_code"] + interval: int = login_response_json["interval"] + expires_at = now + \ + timedelta(seconds=login_response_json["expires_in"]) + logger.info( + "Open https://www.twitch.tv/activate" + ) + logger.info( + f"and enter this code: {user_code}" + ) + logger.info( + f"Hurry up! It will expire in {int(login_response_json['expires_in'] / 60)} minutes!" + ) + # twofa = input("2FA token: ") + # webbrowser.open_new_tab("https://www.twitch.tv/activate") + + post_data = { + "client_id": CLIENT_ID, + "device_code": device_code, + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + } + + while True: + # sleep first, not like the user is gonna enter the code *that* fast + sleep(interval) + login_response = self.send_oauth_request( + "https://id.twitch.tv/oauth2/token", post_data) + if now == expires_at: + logger.error("Code expired. Try again") + break + # 200 means success, 400 means the user haven't entered the code yet + if login_response.status_code != 200: + continue + # { + # "access_token": "40 chars [A-Za-z0-9]", + # "refresh_token": "40 chars [A-Za-z0-9]", + # "scope": [...], + # "token_type": "bearer" + # } + login_response_json = login_response.json() + if "access_token" in login_response_json: + self.set_token(login_response_json["access_token"]) + return self.check_login() + # except RequestInvalid: + # the device_code has expired, request a new code + # continue + # invalidate_after is not None + # account for the expiration landing during the request + # and datetime.now(timezone.utc) >= (invalidate_after - session_timeout) + # ): + # raise RequestInvalid() + else: + if "error_code" in login_response: + err_code = login_response["error_code"] + + logger.error(f"Unknown error: {login_response}") + raise NotImplementedError( + f"Unknown TwitchAPI error code: {err_code}" + ) + + if use_backup_flow: + break + + if use_backup_flow: + # self.set_token(self.login_flow_backup(password)) + self.set_token(self.login_flow_backup()) + return self.check_login() + + return False + + def set_token(self, new_token): + self.token = new_token + self.session.headers.update({"Authorization": f"Bearer {self.token}"}) + + # def send_login_request(self, json_data): + def send_oauth_request(self, url, json_data): + # response = self.session.post("https://passport.twitch.tv/protected_login", json=json_data) + """response = self.session.post("https://passport.twitch.tv/login", json=json_data, headers={ + 'Accept': 'application/vnd.twitchtv.v3+json', + 'Accept-Encoding': 'gzip', + 'Accept-Language': 'en-US', + 'Content-Type': 'application/json; charset=UTF-8', + 'Host': 'passport.twitch.tv' + },)""" + response = self.session.post(url, data=json_data, headers={ + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip', + 'Accept-Language': 'en-US', + "Cache-Control": "no-cache", + "Client-Id": CLIENT_ID, + "Host": "id.twitch.tv", + "Origin": "https://android.tv.twitch.tv", + "Pragma": "no-cache", + "Referer": "https://android.tv.twitch.tv/", + "User-Agent": USER_AGENTS["Android"]["TV"], + "X-Device-Id": self.device_id + },) + return response + + def login_flow_backup(self, password=None): + """Backup OAuth Selenium login + from undetected_chromedriver import ChromeOptions + import seleniumwire.undetected_chromedriver.v2 as uc + from selenium.webdriver.common.by import By + from time import sleep + + HEADLESS = False + + options = uc.ChromeOptions() + if HEADLESS is True: + options.add_argument('--headless') + options.add_argument('--log-level=3') + options.add_argument('--disable-web-security') + options.add_argument('--allow-running-insecure-content') + options.add_argument('--lang=en') + options.add_argument('--no-sandbox') + options.add_argument('--disable-gpu') + # options.add_argument("--user-agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"") + # options.add_argument("--window-size=1920,1080") + # options.set_capability("detach", True) + + logger.info( + 'Now a browser window will open, it will login with your data.') + driver = uc.Chrome( + options=options, use_subprocess=True # , executable_path=EXECUTABLE_PATH + ) + driver.request_interceptor = interceptor + driver.get('https://www.twitch.tv/login') + + driver.find_element(By.ID, 'login-username').send_keys(self.username) + driver.find_element(By.ID, 'password-input').send_keys(password) + sleep(0.3) + driver.execute_script( + 'document.querySelector("#root > div > div.scrollable-area > div.simplebar-scroll-content > div > div > div > div.Layout-sc-nxg1ff-0.gZaqky > form > div > div:nth-child(3) > button > div > div").click()' + ) + + logger.info( + 'Enter your verification code in the browser and wait for the Twitch website to load, then press Enter here.' + ) + input() + + logger.info("Extracting cookies...") + self.cookies = driver.get_cookies() + # print(self.cookies) + # driver.close() + driver.quit() + self.username = self.get_cookie_value("login") + # print(f"self.username: {self.username}") + + if not self.username: + logger.error("Couldn't extract login, probably bad cookies.") + return False + + return self.get_cookie_value("auth-token")""" + + # logger.error("Backup login flow is not available. Use a VPN or wait a while to avoid the CAPTCHA.") + # return False + + """Backup OAuth login flow in case manual captcha solving is required""" + browser = input( + "What browser do you use? Chrome (1), Firefox (2), Other (3): " + ).strip() + if browser not in ("1", "2"): + logger.info("Your browser is unsupported, sorry.") + return None + + input( + "Please login inside your browser of choice (NOT incognito mode) and press Enter..." + ) + logger.info("Loading cookies saved on your computer...") + twitch_domain = ".twitch.tv" + if browser == "1": # chrome + cookie_jar = browser_cookie3.chrome(domain_name=twitch_domain) + else: + cookie_jar = browser_cookie3.firefox(domain_name=twitch_domain) + # logger.info(f"cookie_jar: {cookie_jar}") + cookies_dict = requests.utils.dict_from_cookiejar(cookie_jar) + # logger.info(f"cookies_dict: {cookies_dict}") + self.username = cookies_dict.get("login") + self.shared_cookies = cookies_dict + return cookies_dict.get("auth-token") + + def check_login(self): + if self.login_check_result: + return self.login_check_result + if self.token is None: + return False + + self.login_check_result = self.__set_user_id() + return self.login_check_result + + def save_cookies(self, cookies_file): + logger.info("Saving cookies to your computer..") + cookies_dict = self.session.cookies.get_dict() + # print(f"cookies_dict2pickle: {cookies_dict}") + cookies_dict["auth-token"] = self.token + if "persistent" not in cookies_dict: # saving user id cookies + cookies_dict["persistent"] = self.user_id + + # old way saves only 'auth-token' and 'persistent' + self.cookies = [] + # cookies_dict = self.shared_cookies + # print(f"cookies_dict2pickle: {cookies_dict}") + for cookie_name, value in cookies_dict.items(): + self.cookies.append({"name": cookie_name, "value": value}) + # print(f"cookies2pickle: {self.cookies}") + pickle.dump(self.cookies, open(cookies_file, "wb")) + + def get_cookie_value(self, key): + for cookie in self.cookies: + if cookie["name"] == key: + if cookie["value"] is not None: + return cookie["value"] + return None + + def load_cookies(self, cookies_file): + if os.path.isfile(cookies_file): + self.cookies = pickle.load(open(cookies_file, "rb")) + else: + raise WrongCookiesException("There must be a cookies file!") + + def get_user_id(self): + persistent = self.get_cookie_value("persistent") + user_id = ( + int(persistent.split("%")[ + 0]) if persistent is not None else self.user_id + ) + if user_id is None: + if self.__set_user_id() is True: + return self.user_id + return user_id + + def __set_user_id(self): + json_data = copy.deepcopy(GQLOperations.ReportMenuItem) + json_data["variables"] = {"channelLogin": self.username} + response = self.session.post(GQLOperations.url, json=json_data) + + if response.status_code == 200: + json_response = response.json() + if ( + "data" in json_response + and "user" in json_response["data"] + and json_response["data"]["user"]["id"] is not None + ): + self.user_id = json_response["data"]["user"]["id"] + return True + return False + + def get_auth_token(self): + return self.get_cookie_value("auth-token") diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py new file mode 100644 index 0000000..f81a398 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -0,0 +1,65 @@ +import json +import logging +import time + +from websocket import WebSocketApp, WebSocketConnectionClosedException + +from TwitchChannelPointsMiner.utils import create_nonce + +logger = logging.getLogger(__name__) + + +class TwitchWebSocket(WebSocketApp): + def __init__(self, index, parent_pool, *args, **kw): + super().__init__(*args, **kw) + self.index = index + + self.parent_pool = parent_pool + self.is_closed = False + self.is_opened = False + + self.is_reconnecting = False + self.forced_close = False + + # Custom attribute + self.topics = [] + self.pending_topics = [] + + self.twitch = parent_pool.twitch + self.streamers = parent_pool.streamers + self.events_predictions = parent_pool.events_predictions + + self.last_message_timestamp = None + self.last_message_type_channel = None + + self.last_pong = time.time() + self.last_ping = time.time() + + # def close(self): + # self.forced_close = True + # super().close() + + def listen(self, topic, auth_token=None): + data = {"topics": [str(topic)]} + if topic.is_user_topic() and auth_token is not None: + data["auth_token"] = auth_token + nonce = create_nonce() + self.send({"type": "LISTEN", "nonce": nonce, "data": data}) + + def ping(self): + self.send({"type": "PING"}) + self.last_ping = time.time() + + def send(self, request): + try: + request_str = json.dumps(request, separators=(",", ":")) + logger.debug(f"#{self.index} - Send: {request_str}") + super().send(request_str) + except WebSocketConnectionClosedException: + self.is_closed = True + + def elapsed_last_pong(self): + return (time.time() - self.last_pong) // 60 + + def elapsed_last_ping(self): + return (time.time() - self.last_ping) // 60 diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py new file mode 100644 index 0000000..7af4c48 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -0,0 +1,434 @@ +import json +import logging +import random +import time +# import os +from threading import Thread, Timer +# from pathlib import Path + +from dateutil import parser + +from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction +from TwitchChannelPointsMiner.classes.entities.Message import Message +from TwitchChannelPointsMiner.classes.entities.Raid import Raid +from TwitchChannelPointsMiner.classes.Settings import Events, Settings +from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket +from TwitchChannelPointsMiner.constants import WEBSOCKET +from TwitchChannelPointsMiner.utils import ( + get_streamer_index, + internet_connection_available, +) + +logger = logging.getLogger(__name__) + + +class WebSocketsPool: + __slots__ = ["ws", "twitch", "streamers", "events_predictions"] + + def __init__(self, twitch, streamers, events_predictions): + self.ws = [] + self.twitch = twitch + self.streamers = streamers + self.events_predictions = events_predictions + + """ + API Limits + - Clients can listen to up to 50 topics per connection. Trying to listen to more topics will result in an error message. + - We recommend that a single client IP address establishes no more than 10 simultaneous connections. + The two limits above are likely to be relaxed for approved third-party applications, as we start to better understand third-party requirements. + """ + + def submit(self, topic): + # Check if we need to create a new WebSocket instance + if self.ws == [] or len(self.ws[-1].topics) >= 50: + self.ws.append(self.__new(len(self.ws))) + self.__start(-1) + + self.__submit(-1, topic) + + def __submit(self, index, topic): + # Topic in topics should never happen. Anyway prevent any types of duplicates + if topic not in self.ws[index].topics: + self.ws[index].topics.append(topic) + + if self.ws[index].is_opened is False: + self.ws[index].pending_topics.append(topic) + else: + self.ws[index].listen(topic, self.twitch.twitch_login.get_auth_token()) + + def __new(self, index): + return TwitchWebSocket( + index=index, + parent_pool=self, + url=WEBSOCKET, + on_message=WebSocketsPool.on_message, + on_open=WebSocketsPool.on_open, + on_error=WebSocketsPool.on_error, + on_close=WebSocketsPool.on_close + # on_close=WebSocketsPool.handle_reconnection, # Do nothing. + ) + + def __start(self, index): + if Settings.disable_ssl_cert_verification is True: + import ssl + + thread_ws = Thread( + target=lambda: self.ws[index].run_forever( + sslopt={"cert_reqs": ssl.CERT_NONE} + ) + ) + logger.warn("SSL certificate verification is disabled! Be aware!") + else: + thread_ws = Thread(target=lambda: self.ws[index].run_forever()) + thread_ws.daemon = True + thread_ws.name = f"WebSocket #{self.ws[index].index}" + thread_ws.start() + + def end(self): + for index in range(0, len(self.ws)): + self.ws[index].forced_close = True + self.ws[index].close() + + @staticmethod + def on_open(ws): + def run(): + ws.is_opened = True + ws.ping() + + for topic in ws.pending_topics: + ws.listen(topic, ws.twitch.twitch_login.get_auth_token()) + + while ws.is_closed is False: + # Else: the ws is currently in reconnecting phase, you can't do ping or other operation. + # Probably this ws will be closed very soon with ws.is_closed = True + if ws.is_reconnecting is False: + ws.ping() # We need ping for keep the connection alive + time.sleep(random.uniform(25, 30)) + + if ws.elapsed_last_pong() > 5: + logger.info( + f"#{ws.index} - The last PONG was received more than 5 minutes ago" + ) + WebSocketsPool.handle_reconnection(ws) + + thread_ws = Thread(target=run) + thread_ws.daemon = True + thread_ws.start() + + @staticmethod + def on_error(ws, error): + # Connection lost | [WinError 10054] An existing connection was forcibly closed by the remote host + # Connection already closed | Connection is already closed (raise WebSocketConnectionClosedException) + logger.error(f"#{ws.index} - WebSocket error: {error}") + + @staticmethod + def on_close(ws, close_status_code, close_reason): + logger.info(f"#{ws.index} - WebSocket closed") + # On close please reconnect automatically + WebSocketsPool.handle_reconnection(ws) + + @staticmethod + def handle_reconnection(ws): + # Reconnect only if ws.is_reconnecting is False to prevent more than 1 ws from being created + if ws.is_reconnecting is False: + # Close the current WebSocket. + ws.is_closed = True + ws.keep_running = False + # Reconnect only if ws.forced_close is False (replace the keep_running) + + # Set the current socket as reconnecting status + # So the external ping check will be locked + ws.is_reconnecting = True + + if ws.forced_close is False: + logger.info( + f"#{ws.index} - Reconnecting to Twitch PubSub server in ~60 seconds" + ) + time.sleep(30) + + while internet_connection_available() is False: + random_sleep = random.randint(1, 3) + logger.warning( + f"#{ws.index} - No internet connection available! Retry after {random_sleep}m" + ) + time.sleep(random_sleep * 60) + + # Why not create a new ws on the same array index? Let's try. + self = ws.parent_pool + # Create a new connection. + self.ws[ws.index] = self.__new(ws.index) + + self.__start(ws.index) # Start a new thread. + time.sleep(30) + + for topic in ws.topics: + self.__submit(ws.index, topic) + + @staticmethod + def on_message(ws, message): + logger.debug(f"#{ws.index} - Received: {message.strip()}") + response = json.loads(message) + + if response["type"] == "MESSAGE": + # We should create a Message class ... + message = Message(response["data"]) + + # If we have more than one PubSub connection, messages may be duplicated + # Check the concatenation between message_type.top.channel_id + if ( + ws.last_message_type_channel is not None + and ws.last_message_timestamp is not None + and ws.last_message_timestamp == message.timestamp + and ws.last_message_type_channel == message.identifier + ): + return + + ws.last_message_timestamp = message.timestamp + ws.last_message_type_channel = message.identifier + + streamer_index = get_streamer_index(ws.streamers, message.channel_id) + if streamer_index != -1: + try: + if message.topic == "community-points-user-v1": + if message.type in ["points-earned", "points-spent"]: + balance = message.data["balance"]["balance"] + ws.streamers[streamer_index].channel_points = balance + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[streamer_index].persistent_series( + event_type=message.data["point_gain"]["reason_code"] + if message.type == "points-earned" + else "Spent" + ) + + if message.type == "points-earned": + earned = message.data["point_gain"]["total_points"] + reason_code = message.data["point_gain"]["reason_code"] + + logger.info( + f"+{earned} β†’ {ws.streamers[streamer_index]} - Reason: {reason_code}.", + extra={ + "emoji": ":rocket:", + "event": Events.get(f"GAIN_FOR_{reason_code}"), + }, + ) + ws.streamers[streamer_index].update_history( + reason_code, earned + ) + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[streamer_index].persistent_annotations( + reason_code, f"+{earned} - {reason_code}" + ) + elif message.type == "claim-available": + ws.twitch.claim_bonus( + ws.streamers[streamer_index], + message.data["claim"]["id"], + ) + + elif message.topic == "video-playback-by-id": + # There is stream-up message type, but it's sent earlier than the API updates + if message.type == "stream-up": + ws.streamers[streamer_index].stream_up = time.time() + elif message.type == "stream-down": + if ws.streamers[streamer_index].is_online is True: + ws.streamers[streamer_index].set_offline() + elif message.type == "viewcount": + if ws.streamers[streamer_index].stream_up_elapsed(): + ws.twitch.check_streamer_online( + ws.streamers[streamer_index] + ) + + elif message.topic == "raid": + if message.type == "raid_update_v2": + raid = Raid( + message.message["raid"]["id"], + message.message["raid"]["target_login"], + ) + ws.twitch.update_raid(ws.streamers[streamer_index], raid) + + elif message.topic == "community-moments-channel-v1": + if message.type == "active": + ws.twitch.claim_moment( + ws.streamers[streamer_index], message.data["moment_id"] + ) + + elif message.topic == "predictions-channel-v1": + + event_dict = message.data["event"] + event_id = event_dict["id"] + event_status = event_dict["status"] + + current_tmsp = parser.parse(message.timestamp) + + if ( + message.type == "event-created" + and event_id not in ws.events_predictions + ): + if event_status == "ACTIVE": + prediction_window_seconds = float( + event_dict["prediction_window_seconds"] + ) + # Reduce prediction window by 3/6s - Collect more accurate data for decision + prediction_window_seconds = ws.streamers[ + streamer_index + ].get_prediction_window(prediction_window_seconds) + event = EventPrediction( + ws.streamers[streamer_index], + event_id, + event_dict["title"], + parser.parse(event_dict["created_at"]), + prediction_window_seconds, + event_status, + event_dict["outcomes"], + ) + if ( + ws.streamers[streamer_index].is_online + and event.closing_bet_after(current_tmsp) > 0 + ): + streamer = ws.streamers[streamer_index] + bet_settings = streamer.settings.bet + if ( + bet_settings.minimum_points is None + or streamer.channel_points + > bet_settings.minimum_points + ): + ws.events_predictions[event_id] = event + start_after = event.closing_bet_after( + current_tmsp + ) + + place_bet_thread = Timer( + start_after, + ws.twitch.make_predictions, + (ws.events_predictions[event_id],), + ) + place_bet_thread.daemon = True + place_bet_thread.start() + + logger.info( + f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", + extra={ + "emoji": ":alarm_clock:", + "event": Events.BET_START, + }, + ) + else: + logger.info( + f"{streamer} have only {streamer.channel_points} channel points and the minimum for bet is: {bet_settings.minimum_points}", + extra={ + "emoji": ":pushpin:", + "event": Events.BET_FILTERS, + }, + ) + + elif ( + message.type == "event-updated" + and event_id in ws.events_predictions + ): + ws.events_predictions[event_id].status = event_status + # Game over we can't update anymore the values... The bet was placed! + if ( + ws.events_predictions[event_id].bet_placed is False + and ws.events_predictions[event_id].bet.decision == {} + ): + ws.events_predictions[event_id].bet.update_outcomes( + event_dict["outcomes"] + ) + + elif message.topic == "predictions-user-v1": + event_id = message.data["prediction"]["event_id"] + if event_id in ws.events_predictions: + event_prediction = ws.events_predictions[event_id] + if ( + message.type == "prediction-result" + and event_prediction.bet_confirmed + ): + points = event_prediction.parse_result( + message.data["prediction"]["result"] + ) + + decision = event_prediction.bet.get_decision() + choice = event_prediction.bet.decision["choice"] + + logger.info( + ( + f"{event_prediction} - Decision: {choice}: {decision['title']} " + f"({decision['color']}) - Result: {event_prediction.result['string']}" + ), + extra={ + "emoji": ":bar_chart:", + "event": Events.get( + f"BET_{event_prediction.result['type']}" + ), + }, + ) + + ws.streamers[streamer_index].update_history( + "PREDICTION", points["gained"] + ) + + # Remove duplicate history records from previous message sent in community-points-user-v1 + if event_prediction.result["type"] == "REFUND": + ws.streamers[streamer_index].update_history( + "REFUND", + -points["placed"], + counter=-1, + ) + elif event_prediction.result["type"] == "WIN": + ws.streamers[streamer_index].update_history( + "PREDICTION", + -points["won"], + counter=-1, + ) + + if event_prediction.result["type"]: + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[ + streamer_index + ].persistent_annotations( + event_prediction.result["type"], + f"{ws.events_predictions[event_id].title}", + ) + elif message.type == "prediction-made": + event_prediction.bet_confirmed = True + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[streamer_index].persistent_annotations( + "PREDICTION_MADE", + f"Decision: {event_prediction.bet.decision['choice']} - {event_prediction.title}", + ) + except Exception: + logger.error( + f"Exception raised for topic: {message.topic} and message: {message}", + exc_info=True, + ) + + elif response["type"] == "RESPONSE" and len(response.get("error", "")) > 0: + # raise RuntimeError(f"Error while trying to listen for a topic: {response}") + error_message = response.get("error", "") + logger.error(f"Error while trying to listen for a topic: {error_message}") + + # Check if the error message indicates an authentication issue (ERR_BADAUTH) + if "ERR_BADAUTH" in error_message: + # Inform the user about the potential outdated cookie file + username = ws.twitch.twitch_login.username + logger.error(f"Received the ERR_BADAUTH error, most likely you have an outdated cookie file \"cookies\\{username}.pkl\". Delete this file and try again.") + # Attempt to delete the outdated cookie file + # try: + # cookie_file_path = os.path.join("cookies", f"{username}.pkl") + # if os.path.exists(cookie_file_path): + # os.remove(cookie_file_path) + # logger.info(f"Deleted outdated cookie file for user: {username}") + # else: + # logger.warning(f"Cookie file not found for user: {username}") + # except Exception as e: + # logger.error(f"Error occurred while deleting cookie file: {str(e)}") + + elif response["type"] == "RECONNECT": + logger.info(f"#{ws.index} - Reconnection required") + WebSocketsPool.handle_reconnection(ws) + + elif response["type"] == "PONG": + ws.last_pong = time.time() diff --git a/TwitchChannelPointsMiner/classes/Webhook.py b/TwitchChannelPointsMiner/classes/Webhook.py new file mode 100644 index 0000000..d37fda6 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/Webhook.py @@ -0,0 +1,26 @@ +from textwrap import dedent + +import requests + +from TwitchChannelPointsMiner.classes.Settings import Events + + +class Webhook(object): + __slots__ = ["endpoint", "method", "events"] + + def __init__(self, endpoint: str, method: str, events: list): + self.endpoint = endpoint + self.method = method + self.events = [str(e) for e in events] + + def send(self, message: str, event: Events) -> None: + + if str(event) in self.events: + url = self.endpoint + f"?event_name={str(event)}&message={message}" + + if self.method.lower() == "get": + requests.get(url=url) + elif self.method.lower() == "post": + requests.post(url=url) + else: + raise ValueError("Invalid method, use POST or GET") diff --git a/TwitchChannelPointsMiner/classes/__init__.py b/TwitchChannelPointsMiner/classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py new file mode 100644 index 0000000..b6234d8 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -0,0 +1,315 @@ +import copy +from enum import Enum, auto +from random import uniform + +from millify import millify + +#from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round +from TwitchChannelPointsMiner.utils import float_round + + +class Strategy(Enum): + MOST_VOTED = auto() + HIGH_ODDS = auto() + PERCENTAGE = auto() + SMART_MONEY = auto() + SMART = auto() + + def __str__(self): + return self.name + + +class Condition(Enum): + GT = auto() + LT = auto() + GTE = auto() + LTE = auto() + + def __str__(self): + return self.name + + +class OutcomeKeys(object): + # Real key on Bet dict [''] + PERCENTAGE_USERS = "percentage_users" + ODDS_PERCENTAGE = "odds_percentage" + ODDS = "odds" + TOP_POINTS = "top_points" + # Real key on Bet dict [''] - Sum() + TOTAL_USERS = "total_users" + TOTAL_POINTS = "total_points" + # This key does not exist + DECISION_USERS = "decision_users" + DECISION_POINTS = "decision_points" + + +class DelayMode(Enum): + FROM_START = auto() + FROM_END = auto() + PERCENTAGE = auto() + + def __str__(self): + return self.name + + +class FilterCondition(object): + __slots__ = [ + "by", + "where", + "value", + ] + + def __init__(self, by=None, where=None, value=None, decision=None): + self.by = by + self.where = where + self.value = value + + def __repr__(self): + return f"FilterCondition(by={self.by.upper()}, where={self.where}, value={self.value})" + + +class BetSettings(object): + __slots__ = [ + "strategy", + "percentage", + "percentage_gap", + "max_points", + "minimum_points", + "stealth_mode", + "filter_condition", + "delay", + "delay_mode", + ] + + def __init__( + self, + strategy: Strategy = None, + percentage: int = None, + percentage_gap: int = None, + max_points: int = None, + minimum_points: int = None, + stealth_mode: bool = None, + filter_condition: FilterCondition = None, + delay: float = None, + delay_mode: DelayMode = None, + ): + self.strategy = strategy + self.percentage = percentage + self.percentage_gap = percentage_gap + self.max_points = max_points + self.minimum_points = minimum_points + self.stealth_mode = stealth_mode + self.filter_condition = filter_condition + self.delay = delay + self.delay_mode = delay_mode + + def default(self): + self.strategy = self.strategy if self.strategy is not None else Strategy.SMART + self.percentage = self.percentage if self.percentage is not None else 5 + self.percentage_gap = ( + self.percentage_gap if self.percentage_gap is not None else 20 + ) + self.max_points = self.max_points if self.max_points is not None else 50000 + self.minimum_points = ( + self.minimum_points if self.minimum_points is not None else 0 + ) + self.stealth_mode = ( + self.stealth_mode if self.stealth_mode is not None else False + ) + self.delay = self.delay if self.delay is not None else 6 + self.delay_mode = ( + self.delay_mode if self.delay_mode is not None else DelayMode.FROM_END + ) + + def __repr__(self): + return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, minimum_points={self.minimum_points}, stealth_mode={self.stealth_mode})" + + +class Bet(object): + __slots__ = ["outcomes", "decision", "total_users", "total_points", "settings"] + + def __init__(self, outcomes: list, settings: BetSettings): + self.outcomes = outcomes + self.__clear_outcomes() + self.decision: dict = {} + self.total_users = 0 + self.total_points = 0 + self.settings = settings + + def update_outcomes(self, outcomes): + for index in range(0, len(self.outcomes)): + self.outcomes[index][OutcomeKeys.TOTAL_USERS] = int( + outcomes[index][OutcomeKeys.TOTAL_USERS] + ) + self.outcomes[index][OutcomeKeys.TOTAL_POINTS] = int( + outcomes[index][OutcomeKeys.TOTAL_POINTS] + ) + if outcomes[index]["top_predictors"] != []: + # Sort by points placed by other users + outcomes[index]["top_predictors"] = sorted( + outcomes[index]["top_predictors"], + key=lambda x: x["points"], + reverse=True, + ) + # Get the first elements (most placed) + top_points = outcomes[index]["top_predictors"][0]["points"] + self.outcomes[index][OutcomeKeys.TOP_POINTS] = top_points + + # Inefficient, but otherwise outcomekeys are represented wrong + self.total_points = 0 + self.total_users = 0 + for index in range(0, len(self.outcomes)): + self.total_users += self.outcomes[index][OutcomeKeys.TOTAL_USERS] + self.total_points += self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + + if ( + self.total_users > 0 + and self.total_points > 0 + ): + for index in range(0, len(self.outcomes)): + self.outcomes[index][OutcomeKeys.PERCENTAGE_USERS] = float_round( + (100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) / self.total_users + ) + self.outcomes[index][OutcomeKeys.ODDS] = float_round( + #self.total_points / max(self.outcomes[index][OutcomeKeys.TOTAL_POINTS], 1) + 0 + if self.outcomes[index][OutcomeKeys.TOTAL_POINTS] == 0 + else self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + ) + self.outcomes[index][OutcomeKeys.ODDS_PERCENTAGE] = float_round( + #100 / max(self.outcomes[index][OutcomeKeys.ODDS], 1) + 0 + if self.outcomes[index][OutcomeKeys.ODDS] == 0 + else 100 / self.outcomes[index][OutcomeKeys.ODDS] + ) + + self.__clear_outcomes() + + def __repr__(self): + return f"Bet(total_users={millify(self.total_users)}, total_points={millify(self.total_points)}), decision={self.decision})\n\t\tOutcome A({self.get_outcome(0)})\n\t\tOutcome B({self.get_outcome(1)})" + + def get_decision(self, parsed=False): + #decision = self.outcomes[0 if self.decision["choice"] == "A" else 1] + decision = self.outcomes[self.decision["choice"]] + return decision if parsed is False else Bet.__parse_outcome(decision) + + @staticmethod + def __parse_outcome(outcome): + return f"{outcome['title']} ({outcome['color']}), Points: {millify(outcome[OutcomeKeys.TOTAL_POINTS])}, Users: {millify(outcome[OutcomeKeys.TOTAL_USERS])} ({outcome[OutcomeKeys.PERCENTAGE_USERS]}%), Odds: {outcome[OutcomeKeys.ODDS]} ({outcome[OutcomeKeys.ODDS_PERCENTAGE]}%)" + + def get_outcome(self, index): + return Bet.__parse_outcome(self.outcomes[index]) + + def __clear_outcomes(self): + for index in range(0, len(self.outcomes)): + keys = copy.deepcopy(list(self.outcomes[index].keys())) + for key in keys: + if key not in [ + OutcomeKeys.TOTAL_USERS, + OutcomeKeys.TOTAL_POINTS, + OutcomeKeys.TOP_POINTS, + OutcomeKeys.PERCENTAGE_USERS, + OutcomeKeys.ODDS, + OutcomeKeys.ODDS_PERCENTAGE, + "title", + "color", + "id", + ]: + del self.outcomes[index][key] + for key in [ + OutcomeKeys.PERCENTAGE_USERS, + OutcomeKeys.ODDS, + OutcomeKeys.ODDS_PERCENTAGE, + OutcomeKeys.TOP_POINTS, + ]: + if key not in self.outcomes[index]: + self.outcomes[index][key] = 0 + + '''def __return_choice(self, key) -> str: + return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B"''' + + def __return_choice(self, key) -> int: + largest=0 + for index in range(0, len(self.outcomes)): + if self.outcomes[index][key] > self.outcomes[largest][key]: + largest = index + return largest + + def skip(self) -> bool: + if self.settings.filter_condition is not None: + # key == by , condition == where + key = self.settings.filter_condition.by + condition = self.settings.filter_condition.where + value = self.settings.filter_condition.value + + fixed_key = ( + key + if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS] + else key.replace("decision", "total") + ) + if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]: + compared_value = ( + self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] + ) + else: + #outcome_index = char_decision_as_index(self.decision["choice"]) + outcome_index = self.decision["choice"] + compared_value = self.outcomes[outcome_index][fixed_key] + + # Check if condition is satisfied + if condition == Condition.GT: + if compared_value > value: + return False, compared_value + elif condition == Condition.LT: + if compared_value < value: + return False, compared_value + elif condition == Condition.GTE: + if compared_value >= value: + return False, compared_value + elif condition == Condition.LTE: + if compared_value <= value: + return False, compared_value + return True, compared_value # Else skip the bet + else: + return False, 0 # Default don't skip the bet + + def calculate(self, balance: int) -> dict: + self.decision = {"choice": None, "amount": 0, "id": None} + if self.settings.strategy == Strategy.MOST_VOTED: + self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS) + elif self.settings.strategy == Strategy.HIGH_ODDS: + self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) + elif self.settings.strategy == Strategy.PERCENTAGE: + self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS_PERCENTAGE) + elif self.settings.strategy == Strategy.SMART_MONEY: + self.decision["choice"] = self.__return_choice(OutcomeKeys.TOP_POINTS) + elif self.settings.strategy == Strategy.SMART: + difference = abs( + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] + ) + self.decision["choice"] = ( + self.__return_choice(OutcomeKeys.ODDS) + if difference < self.settings.percentage_gap + else self.__return_choice(OutcomeKeys.TOTAL_USERS) + ) + + if self.decision["choice"] is not None: + #index = char_decision_as_index(self.decision["choice"]) + index = self.decision["choice"] + self.decision["id"] = self.outcomes[index]["id"] + self.decision["amount"] = min( + int(balance * (self.settings.percentage / 100)), + self.settings.max_points, + ) + if ( + self.settings.stealth_mode is True + and self.decision["amount"] + >= self.outcomes[index][OutcomeKeys.TOP_POINTS] + ): + reduce_amount = uniform(1, 5) + self.decision["amount"] = ( + self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount + ) + self.decision["amount"] = int(self.decision["amount"]) + return self.decision diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py new file mode 100644 index 0000000..17f9ace --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -0,0 +1,74 @@ +from datetime import datetime + +from TwitchChannelPointsMiner.classes.entities.Drop import Drop +from TwitchChannelPointsMiner.classes.Settings import Settings + + +class Campaign(object): + __slots__ = [ + "id", + "game", + "name", + "status", + "in_inventory", + "end_at", + "start_at", + "dt_match", + "drops", + "channels", + ] + + def __init__(self, dict): + self.id = dict["id"] + self.game = dict["game"] + self.name = dict["name"] + self.status = dict["status"] + self.channels = ( + [] + if dict["allow"]["channels"] is None + else list(map(lambda x: x["id"], dict["allow"]["channels"])) + ) + self.in_inventory = False + + self.end_at = datetime.strptime(dict["endAt"], "%Y-%m-%dT%H:%M:%SZ") + self.start_at = datetime.strptime(dict["startAt"], "%Y-%m-%dT%H:%M:%SZ") + self.dt_match = self.start_at < datetime.now() < self.end_at + + self.drops = list(map(lambda x: Drop(x), dict["timeBasedDrops"])) + + def __repr__(self): + return f"Campaign(id={self.id}, name={self.name}, game={self.game}, in_inventory={self.in_inventory})" + + def __str__(self): + return ( + f"{self.name}, Game: {self.game['displayName']} - Drops: {len(self.drops)} pcs. - In inventory: {self.in_inventory}" + if Settings.logger.less + else self.__repr__() + ) + + def clear_drops(self): + self.drops = list( + filter(lambda x: x.dt_match is True and x.is_claimed is False, self.drops) + ) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.id == other.id + else: + return False + + def sync_drops(self, drops, callback): + # Iterate all the drops from inventory + for drop in drops: + # Iterate all the drops from out campaigns array + # After id match update with: + # [currentMinutesWatched, hasPreconditionsMet, dropInstanceID, isClaimed] + for i in range(len(self.drops)): + current_id = self.drops[i].id + if drop["id"] == current_id: + self.drops[i].update(drop["self"]) + # If after update we all conditions are meet we can claim the drop + if self.drops[i].is_claimable is True: + claimed = callback(self.drops[i]) + self.drops[i].is_claimed = claimed + break diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py new file mode 100644 index 0000000..b975aaf --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -0,0 +1,103 @@ +from datetime import datetime + +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import percentage + + +class Drop(object): + __slots__ = [ + "id", + "name", + "benefit", + "minutes_required", + "has_preconditions_met", + "current_minutes_watched", + "drop_instance_id", + "is_claimed", + "is_claimable", + "percentage_progress", + "end_at", + "start_at", + "dt_match", + "is_printable", + ] + + def __init__(self, dict): + self.id = dict["id"] + self.name = dict["name"] + self.benefit = ", ".join( + list(set([bf["benefit"]["name"] for bf in dict["benefitEdges"]])) + ) + self.minutes_required = dict["requiredMinutesWatched"] + + self.has_preconditions_met = None # [True, False], None we don't know + self.current_minutes_watched = 0 + self.drop_instance_id = None + self.is_claimed = False + self.is_claimable = False + self.is_printable = False + self.percentage_progress = 0 + + self.end_at = datetime.strptime(dict["endAt"], "%Y-%m-%dT%H:%M:%SZ") + self.start_at = datetime.strptime(dict["startAt"], "%Y-%m-%dT%H:%M:%SZ") + self.dt_match = self.start_at < datetime.now() < self.end_at + + def update( + self, + progress, + ): + self.has_preconditions_met = progress["hasPreconditionsMet"] + + updated_percentage = percentage( + progress["currentMinutesWatched"], self.minutes_required + ) + quarter = round((updated_percentage / 25), 4).is_integer() + self.is_printable = ( + # The new currentMinutesWatched are GT than previous + progress["currentMinutesWatched"] > self.current_minutes_watched + and ( + # The drop is printable when we have a new updated values and: + # - also the percentage It's different and quarter is True (self.current_minutes_watched != 0 for skip boostrap phase) + # - or we have watched 1 and the previous value is 0 - We are collecting a new drop :) + ( + updated_percentage > self.percentage_progress + and quarter is True + and self.current_minutes_watched != 0 + ) + or ( + progress["currentMinutesWatched"] == 1 + and self.current_minutes_watched == 0 + ) + ) + ) + + self.current_minutes_watched = progress["currentMinutesWatched"] + self.drop_instance_id = progress["dropInstanceID"] + self.is_claimed = progress["isClaimed"] + self.is_claimable = ( + self.is_claimed is False and self.drop_instance_id is not None + ) + self.percentage_progress = updated_percentage + + def __repr__(self): + return f"Drop(id={self.id}, name={self.name}, benefit={self.benefit}, minutes_required={self.minutes_required}, has_preconditions_met={self.has_preconditions_met}, current_minutes_watched={self.current_minutes_watched}, percentage_progress={self.percentage_progress}%, drop_instance_id={self.drop_instance_id}, is_claimed={self.is_claimed})" + + def __str__(self): + return ( + f"{self.name} ({self.benefit}) {self.current_minutes_watched}/{self.minutes_required} ({self.percentage_progress}%)" + if Settings.logger.less + else self.__repr__() + ) + + def progress_bar(self): + progress = self.percentage_progress // 2 + remaining = (100 - self.percentage_progress) // 2 + if remaining + progress < 50: + remaining += 50 - (remaining + progress) + return f"|{('β–ˆ' * progress)}{(' ' * remaining)}|\t{self.percentage_progress}% [{self.current_minutes_watched}/{self.minutes_required}]" + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.id == other.id + else: + return False diff --git a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py new file mode 100644 index 0000000..58dc761 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py @@ -0,0 +1,94 @@ +from TwitchChannelPointsMiner.classes.entities.Bet import Bet +from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import _millify, float_round + + +class EventPrediction(object): + __slots__ = [ + "streamer", + "event_id", + "title", + "created_at", + "prediction_window_seconds", + "status", + "result", + "box_fillable", + "bet_confirmed", + "bet_placed", + "bet", + ] + + def __init__( + self, + streamer: Streamer, + event_id, + title, + created_at, + prediction_window_seconds, + status, + outcomes, + ): + self.streamer = streamer + + self.event_id = event_id + self.title = title.strip() + self.created_at = created_at + self.prediction_window_seconds = prediction_window_seconds + self.status = status + self.result: dict = {"string": "", "type": None, "gained": 0} + + self.box_fillable = False + self.bet_confirmed = False + self.bet_placed = False + self.bet = Bet(outcomes, streamer.settings.bet) + + def __repr__(self): + return f"EventPrediction(event_id={self.event_id}, streamer={self.streamer}, title={self.title})" + + def __str__(self): + return ( + f"EventPrediction: {self.streamer} - {self.title}" + if Settings.logger.less + else self.__repr__() + ) + + def elapsed(self, timestamp): + return float_round((timestamp - self.created_at).total_seconds()) + + def closing_bet_after(self, timestamp): + return float_round(self.prediction_window_seconds - self.elapsed(timestamp)) + + def print_recap(self) -> str: + return f"{self}\n\t\t{self.bet}\n\t\tResult: {self.result['string']}" + + def parse_result(self, result) -> dict: + result_type = result["type"] + + points = {} + points["placed"] = ( + self.bet.decision["amount"] if result_type != "REFUND" else 0 + ) + points["won"] = ( + result["points_won"] + if result["points_won"] or result_type == "REFUND" + else 0 + ) + points["gained"] = ( + points["won"] - points["placed"] if result_type != "REFUND" else 0 + ) + points["prefix"] = "+" if points["gained"] >= 0 else "" + + action = ( + "Lost" + if result_type == "LOSE" + else ("Refunded" if result_type == "REFUND" else "Gained") + ) + + self.result = { + "string": f"{result_type}, {action}: {points['prefix']}{_millify(points['gained'])}", + "type": result_type, + "gained": points["gained"], + } + + return points diff --git a/TwitchChannelPointsMiner/classes/entities/Message.py b/TwitchChannelPointsMiner/classes/entities/Message.py new file mode 100644 index 0000000..cb3330a --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Message.py @@ -0,0 +1,69 @@ +import json + +from TwitchChannelPointsMiner.utils import server_time + + +class Message(object): + __slots__ = [ + "topic", + "topic_user", + "message", + "type", + "data", + "timestamp", + "channel_id", + "identifier", + ] + + def __init__(self, data): + self.topic, self.topic_user = data["topic"].split(".") + + self.message = json.loads(data["message"]) + self.type = self.message["type"] + + self.data = self.message["data"] if "data" in self.message else None + + self.timestamp = self.__get_timestamp() + self.channel_id = self.__get_channel_id() + + self.identifier = f"{self.type}.{self.topic}.{self.channel_id}" + + def __repr__(self): + return f"{self.message}" + + def __str__(self): + return f"{self.message}" + + def __get_timestamp(self): + return ( + server_time(self.message) + if self.data is None + else ( + self.data["timestamp"] + if "timestamp" in self.data + else server_time(self.data) + ) + ) + + def __get_channel_id(self): + return ( + self.topic_user + if self.data is None + else ( + self.data["prediction"]["channel_id"] + if "prediction" in self.data + else ( + self.data["claim"]["channel_id"] + if "claim" in self.data + else ( + self.data["channel_id"] + if "channel_id" in self.data + else ( + self.data["balance"]["channel_id"] + if "balance" in self.data + else self.topic_user + ) + ) + ) + ) + ) diff --git a/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py new file mode 100644 index 0000000..8972325 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py @@ -0,0 +1,16 @@ +class PubsubTopic(object): + __slots__ = ["topic", "user_id", "streamer"] + + def __init__(self, topic, user_id=None, streamer=None): + self.topic = topic + self.user_id = user_id + self.streamer = streamer + + def is_user_topic(self): + return self.streamer is None + + def __str__(self): + if self.is_user_topic(): + return f"{self.topic}.{self.user_id}" + else: + return f"{self.topic}.{self.streamer.channel_id}" diff --git a/TwitchChannelPointsMiner/classes/entities/Raid.py b/TwitchChannelPointsMiner/classes/entities/Raid.py new file mode 100644 index 0000000..cd3a525 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Raid.py @@ -0,0 +1,12 @@ +class Raid(object): + __slots__ = ["raid_id", "target_login"] + + def __init__(self, raid_id, target_login): + self.raid_id = raid_id + self.target_login = target_login + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.raid_id == other.raid_id + else: + return False diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py new file mode 100644 index 0000000..50ffc79 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -0,0 +1,107 @@ +import json +import logging +import time +from base64 import b64encode + +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.constants import DROP_ID + +logger = logging.getLogger(__name__) + + +class Stream(object): + __slots__ = [ + "broadcast_id", + "title", + "game", + "tags", + "drops_tags", + "campaigns", + "campaigns_ids", + "viewers_count", + "spade_url", + "payload", + "watch_streak_missing", + "minute_watched", + "__last_update", + "__minute_watched_timestamp", + ] + + def __init__(self): + self.broadcast_id = None + + self.title = None + self.game = {} + self.tags = [] + + self.drops_tags = False + self.campaigns = [] + self.campaigns_ids = [] + + self.viewers_count = 0 + self.__last_update = 0 + + self.spade_url = None + self.payload = None + + self.init_watch_streak() + + def encode_payload(self) -> dict: + json_event = json.dumps(self.payload, separators=(",", ":")) + return {"data": (b64encode(json_event.encode("utf-8"))).decode("utf-8")} + + def update(self, broadcast_id, title, game, tags, viewers_count): + self.broadcast_id = broadcast_id + self.title = title.strip() + self.game = game + # #343 temporary workaround + self.tags = tags or [] + # ------------------------ + self.viewers_count = viewers_count + + self.drops_tags = ( + DROP_ID in [tag["id"] for tag in self.tags] and self.game != {} + ) + self.__last_update = time.time() + + logger.debug(f"Update: {self}") + + def __repr__(self): + return f"Stream(title={self.title}, game={self.__str_game()}, tags={self.__str_tags()})" + + def __str__(self): + return f"{self.title}" if Settings.logger.less else self.__repr__() + + def __str_tags(self): + return ( + None + if self.tags == [] + else ", ".join([tag["localizedName"] for tag in self.tags]) + ) + + def __str_game(self): + return None if self.game in [{}, None] else self.game["displayName"] + + def game_name(self): + return None if self.game in [{}, None] else self.game["name"] + + def game_id(self): + return None if self.game in [{}, None] else self.game["id"] + + def update_required(self): + return self.__last_update == 0 or self.update_elapsed() >= 120 + + def update_elapsed(self): + return 0 if self.__last_update == 0 else (time.time() - self.__last_update) + + def init_watch_streak(self): + self.watch_streak_missing = True + self.minute_watched = 0 + self.__minute_watched_timestamp = 0 + + def update_minute_watched(self): + if self.__minute_watched_timestamp != 0: + self.minute_watched += round( + (time.time() - self.__minute_watched_timestamp) / 60, 5 + ) + self.__minute_watched_timestamp = time.time() diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py new file mode 100644 index 0000000..39e269a --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -0,0 +1,284 @@ +import json +import logging +import os +import time +from datetime import datetime +from threading import Lock + +from TwitchChannelPointsMiner.classes.Chat import ChatPresence, ThreadChat +from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings, DelayMode +from TwitchChannelPointsMiner.classes.entities.Stream import Stream +from TwitchChannelPointsMiner.classes.Settings import Events, Settings +from TwitchChannelPointsMiner.constants import URL +from TwitchChannelPointsMiner.utils import _millify + +logger = logging.getLogger(__name__) + + +class StreamerSettings(object): + __slots__ = [ + "make_predictions", + "follow_raid", + "claim_drops", + "claim_moments", + "watch_streak", + "bet", + "chat", + ] + + def __init__( + self, + make_predictions: bool = None, + follow_raid: bool = None, + claim_drops: bool = None, + claim_moments: bool = None, + watch_streak: bool = None, + bet: BetSettings = None, + chat: ChatPresence = None, + ): + self.make_predictions = make_predictions + self.follow_raid = follow_raid + self.claim_drops = claim_drops + self.claim_moments = claim_moments + self.watch_streak = watch_streak + self.bet = bet + self.chat = chat + + def default(self): + for name in [ + "make_predictions", + "follow_raid", + "claim_drops", + "claim_moments", + "watch_streak", + ]: + if getattr(self, name) is None: + setattr(self, name, True) + if self.bet is None: + self.bet = BetSettings() + if self.chat is None: + self.chat = ChatPresence.ONLINE + + def __repr__(self): + return f"BetSettings(make_predictions={self.make_predictions}, follow_raid={self.follow_raid}, claim_drops={self.claim_drops}, claim_moments={self.claim_moments}, watch_streak={self.watch_streak}, bet={self.bet}, chat={self.chat})" + + +class Streamer(object): + __slots__ = [ + "username", + "channel_id", + "settings", + "is_online", + "stream_up", + "online_at", + "offline_at", + "channel_points", + "minute_watched_requests", + "viewer_is_mod", + "activeMultipliers", + "irc_chat", + "stream", + "raid", + "history", + "streamer_url", + "mutex", + ] + + def __init__(self, username, settings=None): + self.username: str = username.lower().strip() + self.channel_id: str = "" + self.settings = settings + self.is_online = False + self.stream_up = 0 + self.online_at = 0 + self.offline_at = 0 + self.channel_points = 0 + self.minute_watched_requests = None + self.viewer_is_mod = False + self.activeMultipliers = None + self.irc_chat = None + + self.stream = Stream() + + self.raid = None + self.history = {} + + self.streamer_url = f"{URL}/{self.username}" + + self.mutex = Lock() + + def __repr__(self): + return f"Streamer(username={self.username}, channel_id={self.channel_id}, channel_points={_millify(self.channel_points)})" + + def __str__(self): + return ( + f"{self.username} ({_millify(self.channel_points)} points)" + if Settings.logger.less + else self.__repr__() + ) + + def set_offline(self): + if self.is_online is True: + self.offline_at = time.time() + self.is_online = False + + self.toggle_chat() + + logger.info( + f"{self} is Offline!", + extra={ + "emoji": ":sleeping:", + "event": Events.STREAMER_OFFLINE, + }, + ) + + def set_online(self): + if self.is_online is False: + self.online_at = time.time() + self.is_online = True + self.stream.init_watch_streak() + + self.toggle_chat() + + logger.info( + f"{self} is Online!", + extra={ + "emoji": ":partying_face:", + "event": Events.STREAMER_ONLINE, + }, + ) + + def print_history(self): + return ", ".join( + [ + f"{key}({self.history[key]['counter']} times, {_millify(self.history[key]['amount'])} gained)" + for key in sorted(self.history) + if self.history[key]["counter"] != 0 + ] + ) + + def update_history(self, reason_code, earned, counter=1): + if reason_code not in self.history: + self.history[reason_code] = {"counter": 0, "amount": 0} + self.history[reason_code]["counter"] += counter + self.history[reason_code]["amount"] += earned + + if reason_code == "WATCH_STREAK": + self.stream.watch_streak_missing = False + + def stream_up_elapsed(self): + return self.stream_up == 0 or ((time.time() - self.stream_up) > 120) + + def drops_condition(self): + return ( + self.settings.claim_drops is True + and self.is_online is True + # and self.stream.drops_tags is True + and self.stream.campaigns_ids != [] + ) + + def viewer_has_points_multiplier(self): + return self.activeMultipliers is not None and len(self.activeMultipliers) > 0 + + def total_points_multiplier(self): + return ( + sum( + map( + lambda x: x["factor"], + self.activeMultipliers, + ), + ) + if self.activeMultipliers is not None + else 0 + ) + + def get_prediction_window(self, prediction_window_seconds): + delay_mode = self.settings.bet.delay_mode + delay = self.settings.bet.delay + if delay_mode == DelayMode.FROM_START: + return min(delay, prediction_window_seconds) + elif delay_mode == DelayMode.FROM_END: + return max(prediction_window_seconds - delay, 0) + elif delay_mode == DelayMode.PERCENTAGE: + return prediction_window_seconds * delay + else: + return prediction_window_seconds + + # === ANALYTICS === # + def persistent_annotations(self, event_type, event_text): + event_type = event_type.upper() + if event_type in ["WATCH_STREAK", "WIN", "PREDICTION_MADE", "LOSE"]: + primary_color = ( + "#45c1ff" # blue #45c1ff yellow #ffe045 green #36b535 red #ff4545 + if event_type == "WATCH_STREAK" + else ("#ffe045" if event_type == "PREDICTION_MADE" else ("#36b535" if event_type == "WIN" else "#ff4545")) + ) + data = { + "borderColor": primary_color, + "label": { + "style": {"color": "#000", "background": primary_color}, + "text": event_text, + }, + } + self.__save_json("annotations", data) + + def persistent_series(self, event_type="Watch"): + self.__save_json("series", event_type=event_type) + + def __save_json(self, key, data={}, event_type="Watch"): + # https://stackoverflow.com/questions/4676195/why-do-i-need-to-multiply-unix-timestamps-by-1000-in-javascript + now = datetime.now().replace(microsecond=0) + data.update({"x": round(datetime.timestamp(now) * 1000)}) + + if key == "series": + data.update({"y": self.channel_points}) + if event_type is not None: + data.update({"z": event_type.replace("_", " ").title()}) + + fname = os.path.join(Settings.analytics_path, f"{self.username}.json") + temp_fname = fname + '.temp' # Temporary file name + + with self.mutex: + # Create and write to the temporary file + with open(temp_fname, "w") as temp_file: + json_data = json.load( + open(fname, "r")) if os.path.isfile(fname) else {} + if key not in json_data: + json_data[key] = [] + json_data[key].append(data) + json.dump(json_data, temp_file, indent=4) + + # Replace the original file with the temporary file + os.replace(temp_fname, fname) + + def leave_chat(self): + if self.irc_chat is not None: + self.irc_chat.stop() + + # Recreate a new thread to start again + # raise RuntimeError("threads can only be started once") + self.irc_chat = ThreadChat( + self.irc_chat.username, + self.irc_chat.token, + self.username, + ) + + def __join_chat(self): + if self.irc_chat is not None: + if self.irc_chat.is_alive() is False: + self.irc_chat.start() + + def toggle_chat(self): + if self.settings.chat == ChatPresence.ALWAYS: + self.__join_chat() + elif self.settings.chat != ChatPresence.NEVER: + if self.is_online is True: + if self.settings.chat == ChatPresence.ONLINE: + self.__join_chat() + elif self.settings.chat == ChatPresence.OFFLINE: + self.leave_chat() + else: + if self.settings.chat == ChatPresence.ONLINE: + self.leave_chat() + elif self.settings.chat == ChatPresence.OFFLINE: + self.__join_chat() diff --git a/TwitchChannelPointsMiner/classes/entities/__init__.py b/TwitchChannelPointsMiner/classes/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py new file mode 100644 index 0000000..93d7e14 --- /dev/null +++ b/TwitchChannelPointsMiner/constants.py @@ -0,0 +1,199 @@ +# Twitch endpoints +URL = "https://www.twitch.tv" +IRC = "irc.chat.twitch.tv" +IRC_PORT = 6667 +WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" +CLIENT_ID = "ue6666qo983tsx6so1t0vnawi233wa" # TV +# CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" # Browser +# CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App +# CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App +DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" +# CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" # Android App +CLIENT_VERSION = "ef928475-9403-42f2-8a34-55784bd08e16" # Browser + +USER_AGENTS = { + "Windows": { + 'CHROME': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "FIREFOX": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + }, + "Linux": { + "CHROME": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", + "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", + }, + "Android": { + # "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" + "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G977N Build/LMY48Z) tv.twitch.android.app/14.3.2/1403020", + "TV": "Mozilla/5.0 (Linux; Android 7.1; Smart Box C1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" + } +} + +BRANCH = "master" +GITHUB_url = ( + "https://raw.githubusercontent.com/rdavydov/Twitch-Channel-Points-Miner-v2/" + + BRANCH +) + + +class GQLOperations: + url = "https://gql.twitch.tv/gql" + integrity_url = "https://gql.twitch.tv/integrity" + WithIsStreamLiveQuery = { + "operationName": "WithIsStreamLiveQuery", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "04e46329a6786ff3a81c01c50bfa5d725902507a0deb83b0edbf7abe7a3716ea", + } + }, + } + VideoPlayerStreamInfoOverlayChannel = { + "operationName": "VideoPlayerStreamInfoOverlayChannel", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "a5f2e34d626a9f4f5c0204f910bab2194948a9502089be558bb6e779a9e1b3d2", + } + }, + } + ClaimCommunityPoints = { + "operationName": "ClaimCommunityPoints", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "46aaeebe02c99afdf4fc97c7c0cba964124bf6b0af229395f1f6d1feed05b3d0", + } + }, + } + CommunityMomentCallout_Claim = { + "operationName": "CommunityMomentCallout_Claim", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "e2d67415aead910f7f9ceb45a77b750a1e1d9622c936d832328a0689e054db62", + } + }, + } + DropsPage_ClaimDropRewards = { + "operationName": "DropsPage_ClaimDropRewards", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "a455deea71bdc9015b78eb49f4acfbce8baa7ccbedd28e549bb025bd0f751930", + } + }, + } + ChannelPointsContext = { + "operationName": "ChannelPointsContext", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "1530a003a7d374b0380b79db0be0534f30ff46e61cffa2bc0e2468a909fbc024", + } + }, + } + JoinRaid = { + "operationName": "JoinRaid", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "c6a332a86d1087fbbb1a8623aa01bd1313d2386e7c63be60fdb2d1901f01a4ae", + } + }, + } + ModViewChannelQuery = { + "operationName": "ModViewChannelQuery", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "df5d55b6401389afb12d3017c9b2cf1237164220c8ef4ed754eae8188068a807", + } + }, + } + Inventory = { + "operationName": "Inventory", + "variables": {"fetchRewardCampaigns": True}, + # "variables": {}, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "37fea486d6179047c41d0f549088a4c3a7dd60c05c70956a1490262f532dccd9", + } + }, + } + MakePrediction = { + "operationName": "MakePrediction", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "b44682ecc88358817009f20e69d75081b1e58825bb40aa53d5dbadcc17c881d8", + } + }, + } + ViewerDropsDashboard = { + "operationName": "ViewerDropsDashboard", + # "variables": {}, + "variables": {"fetchRewardCampaigns": True}, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "8d5d9b5e3f088f9d1ff39eb2caab11f7a4cf7a3353da9ce82b5778226ff37268", + } + }, + } + DropCampaignDetails = { + "operationName": "DropCampaignDetails", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "f6396f5ffdde867a8f6f6da18286e4baf02e5b98d14689a69b5af320a4c7b7b8", + } + }, + } + DropsHighlightService_AvailableDrops = { + "operationName": "DropsHighlightService_AvailableDrops", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "9a62a09bce5b53e26e64a671e530bc599cb6aab1e5ba3cbd5d85966d3940716f", + } + }, + } + ReportMenuItem = { # Use for replace https://api.twitch.tv/helix/users?login={self.username} + "operationName": "ReportMenuItem", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "8f3628981255345ca5e5453dfd844efffb01d6413a9931498836e6268692a30c", + } + }, + } + PersonalSections = ( + { + "operationName": "PersonalSections", + "variables": { + "input": { + "sectionInputs": ["FOLLOWED_SECTION"], + "recommendationContext": {"platform": "web"}, + }, + "channelLogin": None, + "withChannelUser": False, + "creatorAnniversariesExperimentEnabled": False, + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "9fbdfb00156f754c26bde81eb47436dee146655c92682328457037da1a48ed39", + } + }, + }, + ) + ChannelFollows = { + "operationName": "ChannelFollows", + "variables": {"limit": 100, "order": "ASC"}, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "eecf815273d3d949e5cf0085cc5084cd8a1b5b7b6f7990cf43cb0beadf546907", + } + }, + } diff --git a/TwitchChannelPointsMiner/logger.py b/TwitchChannelPointsMiner/logger.py new file mode 100644 index 0000000..9101a20 --- /dev/null +++ b/TwitchChannelPointsMiner/logger.py @@ -0,0 +1,342 @@ +import logging +import os +import platform +import queue +import pytz +import sys +from datetime import datetime +from logging.handlers import QueueHandler, QueueListener, TimedRotatingFileHandler +from pathlib import Path + +import emoji +from colorama import Fore, init + +from TwitchChannelPointsMiner.classes.Discord import Discord +from TwitchChannelPointsMiner.classes.Webhook import Webhook +from TwitchChannelPointsMiner.classes.Matrix import Matrix +from TwitchChannelPointsMiner.classes.Settings import Events +from TwitchChannelPointsMiner.classes.Telegram import Telegram +from TwitchChannelPointsMiner.classes.Pushover import Pushover +from TwitchChannelPointsMiner.utils import remove_emoji + + +# Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. +class ColorPalette(object): + def __init__(self, **kwargs): + # Init with default values RESET for all and GREEN and RED only for WIN and LOSE bet + # Then set args from kwargs + for k in Events: + setattr(self, str(k), Fore.RESET) + setattr(self, "BET_WIN", Fore.GREEN) + setattr(self, "BET_LOSE", Fore.RED) + + for k in kwargs: + if k.upper() in dir(self) and getattr(self, k.upper()) is not None: + if kwargs[k] in [ + Fore.BLACK, + Fore.RED, + Fore.GREEN, + Fore.YELLOW, + Fore.BLUE, + Fore.MAGENTA, + Fore.CYAN, + Fore.WHITE, + Fore.RESET, + ]: + setattr(self, k.upper(), kwargs[k]) + elif kwargs[k].upper() in [ + "BLACK", + "RED", + "GREEN", + "YELLOW", + "BLUE", + "MAGENTA", + "CYAN", + "WHITE", + "RESET", + ]: + setattr(self, k.upper(), getattr(Fore, kwargs[k].upper())) + + def get(self, key): + color = getattr(self, str(key)) if str(key) in dir(self) else None + return Fore.RESET if color is None else color + + +class LoggerSettings: + __slots__ = [ + "save", + "less", + "console_level", + "console_username", + "time_zone", + "file_level", + "emoji", + "colored", + "color_palette", + "auto_clear", + "telegram", + "discord", + "webhook", + "matrix", + "pushover", + "username" + ] + + def __init__( + self, + save: bool = True, + less: bool = False, + console_level: int = logging.INFO, + console_username: bool = False, + time_zone: str or None = None, + file_level: int = logging.DEBUG, + emoji: bool = platform.system() != "Windows", + colored: bool = False, + color_palette: ColorPalette = ColorPalette(), + auto_clear: bool = True, + telegram: Telegram or None = None, + discord: Discord or None = None, + webhook: Webhook or None = None, + matrix: Matrix or None = None, + pushover: Pushover or None = None, + username: str or None = None + ): + self.save = save + self.less = less + self.console_level = console_level + self.console_username = console_username + self.time_zone = time_zone + self.file_level = file_level + self.emoji = emoji + self.colored = colored + self.color_palette = color_palette + self.auto_clear = auto_clear + self.telegram = telegram + self.discord = discord + self.webhook = webhook + self.matrix = matrix + self.pushover = pushover + self.username = username + + +class FileFormatter(logging.Formatter): + def __init__(self, *, fmt, settings: LoggerSettings, datefmt=None): + self.settings = settings + self.timezone = None + if settings.time_zone: + try: + self.timezone = pytz.timezone(settings.time_zone) + logging.info(f"File logger time zone set to: {self.timezone}") + except pytz.UnknownTimeZoneError: + logging.error( + f"File logger: invalid time zone: {settings.time_zone}") + logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt) + + def formatTime(self, record, datefmt=None): + if self.timezone: + dt = datetime.fromtimestamp(record.created, self.timezone) + else: + dt = datetime.fromtimestamp(record.created) + return dt.strftime(datefmt or self.default_time_format) + + +class GlobalFormatter(logging.Formatter): + def __init__(self, *, fmt, settings: LoggerSettings, datefmt=None): + self.settings = settings + self.timezone = None + if settings.time_zone: + try: + self.timezone = pytz.timezone(settings.time_zone) + logging.info( + f"Console logger time zone set to: {self.timezone}") + except pytz.UnknownTimeZoneError: + logging.error( + f"Console logger: invalid time zone: {settings.time_zone}") + logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt) + + def formatTime(self, record, datefmt=None): + if self.timezone: + dt = datetime.fromtimestamp(record.created, self.timezone) + else: + dt = datetime.fromtimestamp(record.created) + return dt.strftime(datefmt or self.default_time_format) + + def format(self, record): + record.emoji_is_present = ( + record.emoji_is_present if hasattr( + record, "emoji_is_present") else False + ) + if ( + hasattr(record, "emoji") + and self.settings.emoji is True + and record.emoji_is_present is False + ): + record.msg = emoji.emojize( + f"{record.emoji} {record.msg.strip()}", language="alias" + ) + record.emoji_is_present = True + + if self.settings.emoji is False: + if "\u2192" in record.msg: + record.msg = record.msg.replace("\u2192", "-->") + + # With the update of Stream class, the Stream Title may contain emoji + # Full remove using a method from utils. + record.msg = remove_emoji(record.msg) + + record.msg = self.settings.username + record.msg + + if hasattr(record, "event"): + self.telegram(record) + self.discord(record) + self.webhook(record) + self.matrix(record) + self.pushover(record) + + if self.settings.colored is True: + record.msg = ( + f"{self.settings.color_palette.get(record.event)}{record.msg}" + ) + + return super().format(record) + + def telegram(self, record): + skip_telegram = False if hasattr( + record, "skip_telegram") is False else True + + if ( + self.settings.telegram is not None + and skip_telegram is False + and self.settings.telegram.chat_id != 123456789 + ): + self.settings.telegram.send(record.msg, record.event) + + def discord(self, record): + skip_discord = False if hasattr( + record, "skip_discord") is False else True + + if ( + self.settings.discord is not None + and skip_discord is False + and self.settings.discord.webhook_api + != "https://discord.com/api/webhooks/0123456789/0a1B2c3D4e5F6g7H8i9J" + ): + self.settings.discord.send(record.msg, record.event) + + def webhook(self, record): + skip_webhook = False if hasattr( + record, "skip_webhook") is False else True + + if ( + self.settings.webhook is not None + and skip_webhook is False + and self.settings.webhook.endpoint + != "https://example.com/webhook" + ): + self.settings.webhook.send(record.msg, record.event) + + def matrix(self, record): + skip_matrix = False if hasattr( + record, "skip_matrix") is False else True + + if ( + self.settings.matrix is not None + and skip_matrix is False + and self.settings.matrix.room_id != "..." + and self.settings.matrix.access_token + ): + self.settings.matrix.send(record.msg, record.event) + + def pushover(self, record): + skip_pushover = False if hasattr( + record, "skip_pushover") is False else True + + if ( + self.settings.pushover is not None + and skip_pushover is False + and self.settings.pushover.userkey != "YOUR-ACCOUNT-TOKEN" + and self.settings.pushover.token != "YOUR-APPLICATION-TOKEN" + ): + self.settings.pushover.send(record.msg, record.event) + + +def configure_loggers(username, settings): + if settings.colored is True: + init(autoreset=True) + + # Queue handler that will handle the logger queue + logger_queue = queue.Queue(-1) + queue_handler = QueueHandler(logger_queue) + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) + # Add the queue handler to the root logger + # Send log messages to another thread through the queue + root_logger.addHandler(queue_handler) + + # Adding a username to the format based on settings + console_username = "" if settings.console_username is False else f"[{username}] " + + settings.username = console_username + + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(settings.console_level) + console_handler.setFormatter( + GlobalFormatter( + fmt=( + "%(asctime)s - %(levelname)s - [%(funcName)s]: %(message)s" + if settings.less is False + else "%(asctime)s - %(message)s" + ), + datefmt=( + "%d/%m/%y %H:%M:%S" if settings.less is False else "%d/%m %H:%M:%S" + ), + settings=settings, + ) + ) + + if settings.save is True: + logs_path = os.path.join(Path().absolute(), "logs") + Path(logs_path).mkdir(parents=True, exist_ok=True) + if settings.auto_clear is True: + logs_file = os.path.join( + logs_path, + f"{username}.log", + ) + file_handler = TimedRotatingFileHandler( + logs_file, + when="D", + interval=1, + backupCount=7, + encoding="utf-8", + delay=False, + ) + else: + # Getting time zone from the console_handler's formatter since they are the same + tz = "" if console_handler.formatter.timezone is False else console_handler.formatter.timezone + logs_file = os.path.join( + logs_path, + f"{username}.{datetime.now(tz).strftime('%Y%m%d-%H%M%S')}.log", + ) + file_handler = logging.FileHandler(logs_file, "w", "utf-8") + + file_handler.setFormatter( + FileFormatter( + fmt="%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s]: %(message)s", + datefmt="%d/%m/%y %H:%M:%S", + settings=settings + ) + ) + file_handler.setLevel(settings.file_level) + + # Add logger handlers to the logger queue and start the process + queue_listener = QueueListener( + logger_queue, file_handler, console_handler, respect_handler_level=True + ) + queue_listener.start() + return logs_file, queue_listener + else: + queue_listener = QueueListener( + logger_queue, console_handler, respect_handler_level=True + ) + queue_listener.start() + return None, queue_listener diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py new file mode 100644 index 0000000..79d7c70 --- /dev/null +++ b/TwitchChannelPointsMiner/utils.py @@ -0,0 +1,212 @@ +import platform +import re +import socket +import time +from copy import deepcopy +from datetime import datetime, timezone +from os import path +from random import randrange + +import requests +from millify import millify + +from TwitchChannelPointsMiner.constants import USER_AGENTS, GITHUB_url + + +def _millify(input, precision=2): + return millify(input, precision) + + +def get_streamer_index(streamers: list, channel_id) -> int: + try: + return next( + i for i, x in enumerate(streamers) if str(x.channel_id) == str(channel_id) + ) + except StopIteration: + return -1 + + +def float_round(number, ndigits=2): + return round(float(number), ndigits) + + +def server_time(message_data): + return ( + datetime.fromtimestamp( + message_data["server_time"], timezone.utc).isoformat() + + "Z" + if message_data is not None and "server_time" in message_data + else datetime.fromtimestamp(time.time(), timezone.utc).isoformat() + "Z" + ) + + +# https://en.wikipedia.org/wiki/Cryptographic_nonce +def create_nonce(length=30) -> str: + nonce = "" + for i in range(length): + char_index = randrange(0, 10 + 26 + 26) + if char_index < 10: + char = chr(ord("0") + char_index) + elif char_index < 10 + 26: + char = chr(ord("a") + char_index - 10) + else: + char = chr(ord("A") + char_index - 26 - 10) + nonce += char + return nonce + +# for mobile-token + + +def get_user_agent(browser: str) -> str: + """try: + return USER_AGENTS[platform.system()][browser] + except KeyError: + # return USER_AGENTS["Linux"]["FIREFOX"] + # return USER_AGENTS["Windows"]["CHROME"]""" + return USER_AGENTS["Android"]["TV"] + # return USER_AGENTS["Android"]["App"] + + +def remove_emoji(string: str) -> str: + emoji_pattern = re.compile( + "[" + "\U0001F600-\U0001F64F" # emoticons + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U00002500-\U00002587" # chinese char + "\U00002589-\U00002BEF" # I need Unicode Character β€œβ–ˆβ€ (U+2588) + "\U00002702-\U000027B0" + "\U00002702-\U000027B0" + "\U000024C2-\U00002587" + "\U00002589-\U0001F251" + "\U0001f926-\U0001f937" + "\U00010000-\U0010ffff" + "\u2640-\u2642" + "\u2600-\u2B55" + "\u200d" + "\u23cf" + "\u23e9" + "\u231a" + "\ufe0f" # dingbats + "\u3030" + "\u231b" + "\u2328" + "\u23cf" + "\u23e9" + "\u23ea" + "\u23eb" + "\u23ec" + "\u23ed" + "\u23ee" + "\u23ef" + "\u23f0" + "\u23f1" + "\u23f2" + "\u23f3" + "]+", + flags=re.UNICODE, + ) + return emoji_pattern.sub(r"", string) + + +def at_least_one_value_in_settings_is(items, attr, value=True): + for item in items: + if getattr(item.settings, attr) == value: + return True + return False + + +def copy_values_if_none(settings, defaults): + values = list( + filter( + lambda x: x.startswith("__") is False + and callable(getattr(settings, x)) is False, + dir(settings), + ) + ) + + for value in values: + if getattr(settings, value) is None: + setattr(settings, value, getattr(defaults, value)) + return settings + + +def set_default_settings(settings, defaults): + # If no settings was provided use the default settings ... + # If settings was provided but maybe are only partial set + # Get the default values from Settings.streamer_settings + return ( + deepcopy(defaults) + if settings is None + else copy_values_if_none(settings, defaults) + ) + + +'''def char_decision_as_index(char): + return 0 if char == "A" else 1''' + + +def internet_connection_available(host="8.8.8.8", port=53, timeout=3): + try: + socket.setdefaulttimeout(timeout) + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + return True + except socket.error: + return False + + +def percentage(a, b): + return 0 if a == 0 else int((a / b) * 100) + + +def create_chunks(lst, n): + return [lst[i: (i + n)] for i in range(0, len(lst), n)] # noqa: E203 + + +def download_file(name, fpath): + r = requests.get( + path.join(GITHUB_url, name), + headers={"User-Anget": get_user_agent("FIREFOX")}, + stream=True, + ) + if r.status_code == 200: + with open(fpath, "wb") as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + return True + + +def read(fname): + return open(path.join(path.dirname(__file__), fname), encoding="utf-8").read() + + +def init2dict(content): + return dict(re.findall(r"""__([a-z]+)__ = "([^"]+)""", content)) + + +def check_versions(): + try: + current_version = init2dict(read("__init__.py")) + current_version = ( + current_version["version"] if "version" in current_version else "0.0.0" + ) + except Exception: + current_version = "0.0.0" + try: + r = requests.get( + "/".join( + [ + s.strip("/") + for s in [GITHUB_url, "TwitchChannelPointsMiner", "__init__.py"] + ] + ) + ) + github_version = init2dict(r.text) + github_version = ( + github_version["version"] if "version" in github_version else "0.0.0" + ) + except Exception: + github_version = "0.0.0" + return current_version, github_version diff --git a/assets/banner.png b/assets/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..7782fe00e2c62841235d7a2ea294c0ca6b744537 GIT binary patch literal 105907 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 literal 0 HcmV?d00001 diff --git a/assets/chart-analytics-dark.png b/assets/chart-analytics-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..77a68b15a15458c022f1516780188746f47611c5 GIT binary patch literal 130178 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 z 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

HY#4geI96=>UuXB@p0FgCE`Ud!C$N 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 literal 0 HcmV?d00001 diff --git a/assets/chart-analytics-light.png b/assets/chart-analytics-light.png new file mode 100644 index 0000000000000000000000000000000000000000..78989a5af5c60a48b8d5799b6dc06c3740c5acf8 GIT binary patch literal 125507 zcmeFZWm8;R*9A(*ArK%y;~GNau0aBX0F7I4cbCRplVFVmcMa|ijYERFyC%33+?wV# z=R9xK{d9l9-Bnb>E?9fX+-r_8=2(P%R+M^!PKu6%g!D#68l-}RgffhT^wJ*<8F5E~ zs>cuU@!C;Z+XV>;tLORaMItj684}VvBpHyXnrFt*s)sN6Y%|~AT>eHRS{)6_K@t>lYPfG=~mnJRXpRdTsmIqdo z%mxpZK5*}PJrTOsUdMm3UDg z1-#B*h!&!FZ|5lxpGIH8AXe?ht1YOfPcFtzXHN7P{EtnWCTF*YRUK*a^_eYyBfsV5 z=2TK6ei@VFzm<3ynfhK?{4t9!sXfUG>~|boP?YJk(^H~qsNYimquA=Q>5O%BRJO#C zh8$fA89E9o$LHq0`26n!u>cVRpXXm7TKjE94ZQL>Fz`QVe$T8~>AAgaN2jW$5+_d+ zF)S-5D<>h%NPr$nJ!vJXtb8{RyGbWc&h78Zk4pvIdX5Jv;D4P7&8^8XUY=t;gTZRg z0ZHbJ1;y0lX90%R6{>?yBJric4NI)5^1L6UuS-2za55d1RKq7Ck^%y# zoVXU35La4STB*MJGSq&#zNERv=}Dv|WszfaBQEYNz9^&C=uekK#1IqHhle+AABk#) zW32|`Xor*8m5Qdt^#S+W`TFAe!5t`KeUWwLa`vPL-bhH~AO0H`Djl$5bhKQJhV&{B z8(d`)>S#2&90KNoVX*AJ+8xQG}Is5DNyLqSos7-_ma zXWnJi!odtqX@(XSI(OeM(q|BV@5QMl*FI^#Su2oB#&Tm_dUF^Q41tW`v7zhTC+1Y5 zApNA`>UcbMz;=5M4x}ig60`GbN*Hz+esYpdk_70*G8;1`=ff>t;dqL5+D=wWONA~} zO;dgLbTN;cM7-W}O>}h>7jW!VVpi+DJLa+OMV~$zNh-*TkG|?LUiAd;7g@@g*xH{? zR6)~@dgrn3*Lc_>t{>r^j1R?fBf$E^cT`WPCp^=Dgv1X@kwZiD;fEn`(#l`-mr}>m z>Unr9BGUTWP%!!xGKi~>b2m0rcgIEGJ$Tb4YNn=}E=ofFPjR$MZdd{+0KmLkna9cP zv~cI5zWUYVxG!D2EZ@;$-JeX4O9IWfuyqHiI5V#OMo&^Kxpl{4ne1VC=i+rvq%Y#3 z0XfeBdf&@4dcp4ZB=U4hNjBGUv3Bo4dAk}HS6j=u*&qE@u<}QfR{dIo-@|>wsoiRQ zn2@HEqF;?f&qbRV@a(o5yI^|%3%E`t43tg?sCYf96~zFje=!Vae}NSBSz7CPez77` zWlF+1^Tt1(!sy@n54e%fJ3+yVwSL1%m91_Dlvo47xsax)i_1`DC&wE4`SW>yk25z(Ang(mCx@8S-g97iRWPIK- zCyts?KK8HIrH&+!a4YJ2_Prygcm-N0$Ig#b;COj{e4E|~89iLn|&0?JQC9i&K?|PQ+Z(X=|mAQ@! zdyUe=4@jY!?E$68#As{lv1z8WgwdKgXsg_d?E)5OF#H0EU+jOqW$)(bQ%JdmWBctz zcf#Fx<4H!!MCq1lz=xNMb;FX8Xra`B?s@~t7y2Pz*?Kx(qu#b=IUz4VkW%% ziGX?g#lJhpyRo}FiO~X&JBE8ZIv&$xpaj=TGLN;Cg$5Z=Uxxo#{mpvD{pc?2Sf;=3 z;daY~L$=@W^0E;=DhE|)KgXcn3Fz@Tx~>!~{s!>fc*urTmDQ&UA1()|9U7jM@{&W7 zF_1VGVyd6l?2?FE>inEg-|>&2L;KZEt+^tvV2aed4!c%;vB`{8H^-}srkIEh6MjId zpvQ>>k^bc-#ENUe85=g%IOaU{%NcfvS?Q1gI0X z&7SRhK3I#0G1I;kznfJ-_ajLEW&{zt#f}xv{pGZh$iq<`$aT>?c{J_49O&TS!2e-+ z-8*aE);v;~d!?;5^XV4$O&QRe;7Xcxx1_FWqDKyeQ4}TzfvF`yZi1R6aaA72FXrbX zhUjz*?&fCcqer;qFfwxET1qL-{B9CtijF-uo4w4FHhxBFW)pMn^DH*NW5!vLemdrb zwEUZtrEm+>dW$D1?|X{q6jO6&em^GQ##SUIMk9Jp^X+wd^t&mt03S=!qzJZ_;@aBt z`@^cv!^+k%{!Pq);TE?;_@)SaGr|zIA+VdFs-wdM^z!l=PGWudgB@_YU>bebn&~Op$rRnAZy73+4x+Xw)hLVx>7& zW8M&5dpjfcy3bbK&!cm6lpjtLupC1bfrp_ct+Y1OPPwg$@)rl3-Oc1h>$%<$Y|<-P zSOl?11!cvG&S!KPrg9i}t;4Syb8~aS%g|N#W$>A&?^M1F3JQv4#^lE(MJFdG1%>as zUdvE$ME_J`hg8-0`q_N=QUhrPq93)Bm;(?1T8D&A#ug7h{593^-^!JUp%y zpXRS39e?sXD*T$R#AItr(`sIxs~7&u8W5VR1pCyC#o4F<)($ugpd>o6>vsM!sj~LC z8#j9M=)G11UyqD_v^u1G1ln0&?>vVsqz%3h0MxVFjJJ4e)LDfV)022b|cs z4}=km(253LOz{?Z%zDf5tmpvPCOu;Dv@J8;_6w5z9}f*Z$akA}vs!nvd|)pZp_$3a zh(N_%o+rwD*bm|waG4jmE~*HCgC}S3EcM~qHtSLb9anwO%tR3WYvSo}abT(KmEiDe zR+yC|TpcvyJpLw5hQu7N&)(Zqer4@$sas;+w{v-ToaYFOD7lfIcRFNA@!?kmJ zr?AZY=ogu*jE{z=ypc&{S~m`49w;P8_c0jO9lvq&M+7nGe?dHvvIsrin5vx~6RIoD z-^y&%sL*ZqmfQ9TgF>S!@oA2UazP;Z+ys?yg1RCJTUN2qnMtYZqe|_%^HCSJHmgsD zIOJ^lJ};G%adKQFY#)CIVz0SQD~Q1UV5i<)FFVZZh`|1K^Q|~`-*nsvPxB8Hul zJ&w!HUsD5Bw6(pu-|+d~c2n{yxF8Tus=Ngvu9ZICZFzKDP$2ShX(`lhaq7wuzO@eb zHu%);wwr9>d)s$j1q<%M8?VrT&+G)OadD4x&K?6AS*zp_HQjb}9?W3Y`=ZL@Cf zfM{ND?3b@GC`eJHkx${z>5`ljFqyU6Vy|kt>2uoAdVSnj$~;eB*63A<$1w^4r-lIP zD;$5K>sW9YEftpF3uetHjq%pgm67T4X&je;?GK-nxGZ=TF1`)3Jq4`aRf1s^8ng6p$Xc-t8CjzokPvWeH)s!*{RLAG+sOY<3{wmqYy@`cu*C&hlQ zy)8>SAd%=^S%pl2ixZJmhvjQyKV^=0Y@M+4oXReP&Ww87;xb1g^Tu-G117|B055T$ z_uv5n?&V)bKYvS$elAo1%qzztTbI!8_`!FlH%dq9nCCR?%kC!xF4!p4d42Tm{Nyy- zm7D7fJvu<7H#2-Qvx(8)v+FG)XP2mj-xJ%r$0Q5)tCQHvp$EOXeS?HV{6EzCP7|K;Ly<`ov?0(^la^ty z<3Jc&eh7aona}dQs|eYFmLFEGw#n62FveXs1%+LUys9i}@4FY?JM;r-H91nf zakF#!hgG~kn{VP2(d*x$WdCm3dSL_i?`{1br**pxHoCYKGq}w&%#V)GCsLEFV(ATZ zu5Kfk)0g8wG&+URlrrJLqaGf7q~T#D9E6#iPW9%gWQ`|F>dOo|H*t9ovngvHLjj;( zz~dG0d?SJ#D>R9vyZH?Uir62#vFLz3vX^kgGcig3qa*@&i_Lzzvz4W#nS$AdH7)fV z{_t;jgrkD?W->Wk3wzR9dgIeNk_q?;M7uJq3fw35ZCH50QF*9RFRO~pw07aBj zx!w)vxD8NZ7ks=enb*7PnCfckRQk!oE@-)CzB_`4YeB1%A`?_U7iQ+&RXo+JX{eQv zEd|>0y8HE0YAoAuedfN4%(kJ?L5^?-iS9Yhh%NqubX(zxXCZLNjvyfu)bjRL&IoB;E>_^57%=gx2h%1`ouIJ~UK%pS8DY{sOsO<+-#pg_>*y+$ z>rFlUxZOzHBp3-I`d@diAfA9p|KBK_dq=3HqC3uP4Xc*hTivHy!)Dq0zM!{`=M_Svr}Xh~8k&jg)gw12Ydl z)p=7CWZpuXSBO#A|2GOuBnv3=*YL?aW=Rb**_ZZ@l^#ruA`dWI%S<7claa8@){4cZ z<2wS|(gV_{bgn`M5gUeH`4Hm_4nJQ}mM&1nXxt$L9IrBhU;8r28s}^+Rx?FzXh?Qw z=->7+ng6#Sl0I8Gn03Cj_LV+c%>-5L?VPbgh&I|(hStRwjRoU&=VtgCXoZ!?WC3+K zx8W{I7|Sm-WOsWR$c^WX0aUVu+);;aZ^anRb>>!7l^R&eqII5M%85_^(r%6)DeC#3 z(!T~32`TYG>&+?Ht(bu(x!QG>O6zS!E$&J@62V}Gh(hI7nmzM!*v!v5w>_C4mp;ZS~>fwwG3R^+Q!?F*y7nLTZxz9EixnjArQUX;}a4;6Xjy{t_}dIBtDZBYFlZ zpE9R0C)EnMpp#d1p7k>RVF5|;&L-5`z%xUObDQmK%u_Tt}&Ojuj$57sX zb+(szkLXIET1KbLA0>rRWss1-)m(hK4SzmSMz+Y&G$A@tEEMDyg2Z;^RX2K#hZH{i zDwv_X6#eJ!NMA(1{I3o1AR&MfhUFAZR%dTO5Tu32OaLv(IK%6_d^WvQ(R00*{o|dd zsIrrS`s~7MyjSH@jw#j^f)_lU;NEJT(~)-}GJdaoDUggf{v(P5VT4Y>dP+h7RuBy| zofZhBqMCZ0#0!j%6#6p;P`?kj>)(`2(t8LVCm7`~K`0O0Y;8@Y&A^NxI(9wms-azx&G@1l<)u&|S>#CIm$N__(ixH+g%uIlb@d=H}M; zXgQK{b~hdXpOm8XJ18l-R^9QR`>qAwT877ztcil`Que{=rB8pkWoRFkL>$(-lD64# zl|9c#HsnNVAL-YH>Nl?&-ezz#9331k)Jx}IS9L0s`;_f|%wen>dOE%UB`me&+~0^$ zSNNQ~MB=bQyl6jb3PnVkqY5LE#68z^rv;l_ELf*AbLuzVo`*w z8v$-Xkkp_snL1Td=*PjuQ5G!Dh@qRVqP+<{&RoMAitFk0ZLV%M|i-&2b`E02=dne4<*f_`aU2Z%At*v^jSCp0> zK%Xx6rzw5^{*rp@h0w!}ibg8MlU!}PSA)YeMxS*EJ(Y>82;M4vdu+9aRdwGN^RSW5}ew*9oeM&9@s7+^La= zEuD9x4y2@{kiC=EQ-coQW(e%WqjOnlRgsDL@6$@%{rA7Ay8vagzSq4(evXH%hSa$e zTO{U`u$d%Em}M`$16maE`wdmYavf(>0ONG|?afp5u+)K_`-4(PPMtO|JhT4C-nU^l zI)yCFhAph3nk-1+8oVgPlJZSLRJBy}y4$!CmtbSKblJ`%pC6xYd)--ZziK+~ErBPD z%#f5aB9fK4x-SY`UU@+J>W=wON&hT(Rfubmqq*E@mL1**#0`Hnl2wI)WHf;QoL@{s z_m<4P4-I>)sU|@)KE6g0BZE(svWgUOOjyJKlgT#@KBsGd7;bj)VF`2i83a<6b6$p{TSfEzw*dJaC>3)D+#mnt-);A7Cjj$X~)@jhYbJw z{c)j-$W8u5ySep6hb0S9R?S{$KC;@Sm@0|1 zTC9?an?0ltqgQo|u`sxk9(<$Eg3sk)_klE4I=6!2!jtVE))Kx6Q4Lb^?GKIz)R*MI z{nlHBc^+?}jY(v&R*Ri}@SMYs2T)!muP60Qwg=DAb`e@&B2j?DBJ=Fwd{pz^(B#Fq z%+vN;;L_P;UX{pYqcV4Ny4N7VHf4L|y;7$Rb>qBt$iw65*x;<~tM3Iu3dP;QMgC%n7LQ|%o7_WvCf$1cbn%2A-D4`i z*~yH9odhnD#d9`M1fhHR2A zfqw#IxGL>kT$h%~sy>e&S^|JIjqs50UFE~pnQD0+cRK!CiHgHsNOO%``-Dun z?&zFl;^ApZt^728D`Z-X30xqD!%~IvJ!>I=H6`xKOf!V#Mk!?RL zuX~tX^;q30Zp^{KMtDtW<9jw}z23>);B;I$>p;j<(Xj4X#yyBLw`4Y_!)&1m0;yDz zS#qUH(Abb<6$u*T5xq}@Fj%ZQ?G4l~O6=;H^qljFTr7xm`i^QXvtXibUJwq|5-GB> zFI-&Pmj1S7B0s|g>l->iJ1Q%kyBClc1#3-vnF}`QTro6oYLR}v6aSBstyu#!X|`oc zsSR=}SJ59f3f`EjIMV}c%SJ}imF&V}Pf;wb>t-BqO!GBr=c}JASdP3tXq0xJJswS~ z`nZrO;YOV-rN0gdzx)J>r&B*&l2q-`|7B$~qrr#rgc87W5&6Fc{3Y?(2dm02L~vduN{Y8&>Kx94y&ZHGm%yH4XN?-T`EarW+B;>?q%Q#-N!> z$xKD#8q)(*cfZVAKYHr>VB2PMKSovauO$P<7x564*0MxnMc92s;`)=UPj-F#8G4xX zyG-x1YsxJDO%IxQ*}^H?liO@gnpNzX$z)a@r=_k%dJ>B4sa5Rt^|tfbqK>Bq2}8I| zFxV&S=IF^6j2RDE*rfa3cJFfPcYy)IC)iEQUT^ukL&-2*YS0P2zUvPUb>VqcB<<)KT<0ooZBu^FzH4R2w0Kcrmy6Vbt4NpHU&egZf(mY_49$I z%Tg_1okK+F?r}BG(!#vNiIP`RUsfoCR4iQDCb@CjU%#>KG&3c!m-b?$BtquT+n1Lz z5luR^X?g*bKLUNBLq}o4onP8%tfkUPD~cum&kJz3&&VgRtyx@Xlh@4k*(ZAuhFpWI z^1(NbtW`czi*j=nJ@z;!-~ecKwhkPbbKo z@a-Ei&}1hhZcCa*jDouPALOrHU0wEWdn`GJbuIRsVTi46uQ@u^kpB678eWStbhcWGwEGG#5WD)WJy~8!HZ!Gn3UYNuW?2` zB6->pt0D(LT+Z2y4ZlKR+*qxjLLHu`M(uYmE^edz@})+d*h!FHksuWLFL`7)TDyKw zO1xwpc}YkGgSMBQ#3ClPo1ak%E|t__Ieq0(6_ut4y0b*^!Y~dXf_F-N07()cI!o#*@g;sh{v!&RV_g?7Mbxo7MDzKOU_&oDIOUYfs0f zj_vD?9ClB(J$#0~G#KwU(Jlz|y9AWhbe`Y)7e{|M^{c0he5cDY~7J->Uc z<2-Kjb5bdSKg&$$c-wbeSMAe?kTYo*BxS(*y$;{OMsV&o6Un8{S)hSuevGJH+_~V& z@q-K=ZZ{z_$7cmaP!IJGLR3O7 zrN07A3dKo73q&j-GKWi=1z^F(=nRFRIeIDDrxQrlpFg zY1~~SLqs)^%`QNLJq5E>SeH{QKk_=6C2jh>_pXQ_)^2v-So{pYx}k~#1zim;kKQ8_ z72BcK&!EJx8J_2|M>3NLAZY#U!3vkyR?MW(6H=<^64@&{Kr-g+j{2_?kv5U-a zr23w~Xz^ust=SDVh!8Px5ad_s=#=}vsvZAy6=iwb6x%vQG70%je%3@3+F74Pt81#I z*un;ot&d+qqNx2hrm%sA>at+tl7yoD!mhxvGi#;Vuwv=Ox!hQH4`K4~NcrDlJgHZBf0-5nZ;&27^mLE4W;wS^*PVf38o49os zeOB!1%;qmIILv|uthYDfm&cjN^+7=p{n+}v_K$IxBDxp##r{#a%<>jAy3U1*8YIPV zqF9{tG=*&*E0=makue7T$x=f}gK)J8W}&ea{du`$nu@SDTrWdSF<(;+*|6qA28=NM zd=~WoPw2g8)ea%^OBmY&FXZH+>_)bqh=jHPksR&*HNONZ^yF=^IrLL|kCScDVt+7L z#5RxB@A=A4MrGoHq?75%Xl)>6fI!nuO&O30>%C$s&`8D=&9V2xK1sd=>9nHD5(S{9 zTSMfVw1rBNu5aOj`Pb3_Tuw&aBQac{`N2ZuNdBRlS%&QKP!zm4U1WIWf@|OIhD#`# ze1A{aRf}H-qvVH)c#hYcaU((y?u{VAc4I|2hN}N{g$2q$AWKkh!M8~v>RVWQ6#Bf2 zY|1&3DmzHq@5DeGbOq)wBl2W-Ts(VzVe`D~#>XoV9&B3k_kLwA)Gn9`9^{v|V>sFM zVJ~;}eT5RyK%#ChR-U5iS-K$6Ba19y|4WD>eQaq`<7*79WK5)0d{VLXKwN}|7;4@!`_y(rgApD=EAsTZE$MwL% zx~UeK!*V9wft=Xm{%_Q&kbL2@P`_i_WZwQV8MF8(dNFgru*xGP6ck2s6Y02-@DO>w zZU0B6#WAZ|B43&jD90R7XwaFL)o|)K+@+X=2d>n5uRi+Pnw1Ux6*w?8?3K3(zJ(IQ zvFrkV@R}H+O9KB*lely}Pv;@n>yr+%OsfFV!tnMxJjy61W`x#E4PLHKR;M2OJ4*PXv*$ZoN$wcIWyr)3|1ujv{L&F%wUn6v2v6gx{2}Rv> zbVLY6e1PD&^h}JuG^t?QJ{l0$b8XOfC1fS^58sM}oOANpU2$nsOr9yir_`)cId$3Y$sA&}ZK2N8yN_=R$W2Ok zSQ{%%`ygIyH!65lHh=UwwA_n6G4r8=vo~0DntZ_F%ce}Y+X8WT6#+tbR{rN%1}694 zI4ErJo<613R)`yJBS5IMDEwMt?#d5Izo*aiPYm)!D^Gb7On=w89^Nc#FUK6M}mONb-NL!P;Bqj`kh^*fe&l2dD zZiOC&!=0-S+KJ+a1R}aMi)=qWy4Yl6HHamAfET+` z5dX42j#e&--7rhB2(@i%=g^S!FBf*`4}igE&%vurfBqQ)gy*O2xs34Bxc|tK%2^tZ zo4ZCdL_y;%D!LX5#EZc)d3)@s-)%1zgkj$P_`O{WlqEfN#f9Jw$Dvq%z5t++FT%)S zzBI9}njPvlThqE_wsCOiBsD$EWU()kb?r5Q3Vv=e?7LO{BK%(<98GZ0P!0s`5q z#WxB;DvUS1$z?W&DnuEA7E?jK!=zul(Uq5G?U;DXNIoIlF=Pi!m_+QeBH@VbCd&Tb zv{oP=rD)VT@g(;ljitV-`j;0T%y8H+eSDF|#taF(i@1~MtSAoWyQf0A_>4UPg z+dqu*l3&V+OWt{a8-bt^^0%TRG|n+K7D_JO@W|K>`NE0Ad3 z5LDed_$*CZYwA_hWz7L;@axrjNWU<;gFp~n-4z0d8R4gm>w`3nf`wZ7VXk`m{YcZa zuK@GBb5j&D=J+^;Q1L(CU>lD;%oM$Go1V7wvqdT=%@LBL83y=R*gw^_vp)CX_4;&% z#^jlC4UPbv`I9=jHQ_(mRO5&bJYFLG{`>6togj#>{T_bNWM!Y-%5bspc3lmk!1hsH zJ!3wW$4LrlwnmMPc>7&+7dg{r_(dwF zMUvk%0UfT>)xAQf?T{rgde*vk!6`+`0D(;NMp%;$IV-3~zUGTH_Qd6fu%Mg!p?k}$ zb!gE?0Q&N;7z)fqS!G)nu6QTd$OKfpSHgU@)g$EFpHDvwNea-svefKtY`_W)v|L45 zdI0q8x3!SG-oFXol^sx{*3DiIcSxqZLgKf1uBk7e)&aXMn3o>9Lmg!T)VPq%B%0@; zLIN2;3ki#a#Ul-51X?~xe^8JfFrw}nqKV6*jlXt8apZAo7_&ONCtpc|NJr!gISCzG zJGuI(CV$BRCOPe?qPfM&?$6q$8TfihxyaFVaxYSt<8z2 ze8k73JnHv(h)N2jT>UA3p-4)CwS9RwRuz@kJK_D?Tbg3Je$J|Us+s)%wFuwW6LO*r zzur0x95-GzSJk-=6b+!o>C2Bi2tEm1l&zmJcP8ikM*eM;E5Vh;kBUAjl#{>tI+i}} z3igVH{0%WQ_Tq01FsqPtIc}V7RYx!-i(qQyoKLPgS(kCi@+T_#77(9Kr|VSrR!4jN$i%idfRwTQ@5W$Q_4X(T73J3Bh$zcn`peXy{%j5R=tkg&5^*x#P=S)nd9c=^>q*r z>e9*S;NUkuuLuFRMfxoQTyWOz{G9s z?>*keySX=p=Z6y)459nw$Viw2)(Mf&X&$8pcDfUp@}M>gi-#*`gz+nG0=#e_E&LR zmEWN(i{%DT-`%E~vk3MS+%PSHYi}2c+AOTvu3OdSzx~GhExp+9VaFlHEP73j$4d>F zfu*<0;yDxFP%z$l?@r;AheFxM7Ien*d?Ndp0SWc=JT|5y*&7`@S|a z1)Jt({ahWesJ#}h4M-))=9|p8XntU)-l@ubjIWcc^z^|r;R9S)+l zh1<(SFkcn3xjtxPRrzh#TxPm|$I-(JEZEh0Qv2H9m6OXT*Lb|4=z!xpC8daw$Gh#P z+nd^r4Is800@jQ(UgWTg_ImX9GyEvX41K%G;ioegX-!+0Iz5a-r6i3cl45?`e_+b^_ghuX~E{v1i0aR=%cKDfKK z#m;_jC2f@P;^LjMlb#86trxX<%!BP5_(f0Mcp+Q>_s<#N%df8Rajc=Giqi$)L2fsQ_=IQOze)Q6c-?DVr~;dQ|;*)Q$N&#Phc7IdgLw0px_h zTCgBH`}q3tvs+UfTi8r{t}5X1P$8WcB~TQgU28T-9WBHa0Ea!_4M2GKG%EELZU2-l z8MHCMPrutAFTbxMSl;H&E0PZzh09zsljHDJWa~Wr9@q3uhVBX5fV$ZJ0s==_&sQ$e z6IE(Fd&c=Q%9rQjMl3S)WY@4yQg$OC^+}DI^KC6Ap>qNaq&QRnr+Oo-3q4o&ryMo~ z#gyhIaTdbEm1!tAU>MxEdX2EQ3gDnEK>b$T=FPcM```K;#Y(+R<#z%eFS{1h!PJVw z>eF-`=LEr36<~cQ_cMalG^p|dZSHKt@K}nk|6sW^T>h{n!XfRwoBKR=UsBSWJd;g~ za$LaO`AjysVf81F#(X({$rcL`z4&*1oB$b_CSJohbl!g@N*pgX##LC>H^ghTmyd!E ztT^tCHK&b=L`?dxr8AVQj%y(@oPfG(+?KzMob?eyi{S_Vv3FEi@WL6rnlV>m?Hlt_ z@lF(4u}UfCwFOn(c@4d!^~p+5+@QRz?RiXu@Q*BUlEn=QVfN;dzSz4c{l&4dKSZrz&a7tps7N6@dt=@ri%J zQY&qfQvE+jjx+xXUcmvjV+sh7Z|QWlCcxJIG`w~IjnWkl6F``}$~(jhc-;)9K|Sex zXOLDqnyYUCWEW=f&p{7E^RjkMd!$nPFcxQ_;-eHxapm7VM^QuPS}icf#1aU59Z zy24^^aX{``??+w3HHh}MGWoUCyC+5OrNeomUNw1|QIhBsiJr-HxNnDemUTNI$I}+04p?XptfA_v@IU8adwL!RL^$fJ8ONRw|olI z>z%ruzeYOsE>^SKFEe3_bm{k(%aRg(#J`*aQ%4zAJMW@stDb@gnA3Cd6bQZRu7i`; zyxBgk0%@ySmDnDSJ0Sy+&TIe3f%0k245I^Ng>YHjO8_j&pn9{bif{p~9CzAA-^&h^ zyE<0j{cZMmlp@zi^?@_K3h%B$ZE;>!W&;Wmf6Fry-4hFHOK5x))qj*f^+F;e&$fts z+M|ku73=blRw+< z+ml-ir}V|zZqYrl-1#V5*zSPN9HppYZi&O)H-tioXD)Q?jXK5_HlW4=dOc4Kq&hFc z!=;jQn)ux8o1bFU9g?)#!1_XPVlK(sr8muypWpjvKn<8KUV=K>S@<}QcdG9R&xd(( zBu{Du4V=~YhJ2-Zk0mn>Wg5{_QJHdSUDq#m@i8!X3$U)po3xroez55V0{={gOU8>B z$^IE6bhE}cjW+TLS~NH0I%jYw{gZ68qbk(-sC>MRWE7^z+d{|&%t4=p<09jWj9tn_ zjdFdkow{NG`W%LNA{ZLrUxr4jqtZ&-o(?Hq{%%KQB95{VIx{OFUF2#a<+C?YwVnn{ zeKj}jmOK04Q8!7(!u2$U~T z`)Etk1xnh8q(8pk@o;5P2}drhbw<+yF~k=nI?P(J=2Yu<7Exu&v{tsd__!%6Pf`&r zQ|cW0eTc*fllJpFFVJ+?bN;hMuA2>sp$8I+?8itaYlqjYJzcy)a-{i}ZmA~kTniU| z`HOlZm~iMVqAt+%;2HWi;n!rI6fz^%H?!>;{ZASn&vM=@PCU&IchlN`GwwdSe~*lT z7)yg|zwcP(oPUN52P*z)C*Sf{X}`c$uuouib-oJJ1aTN@+tJX#hE>>-(PW1{t5VN( zu1R`f=xnc&@OzY4C=D@Yp-Rb^NGo(_z^{GTlcC#J*ed8X((mFB(P5{k5D-#9k<(rjmR!LHS`ZKqT+?+weE;%`UhFt; z_ByZxpR*t%icL)C1FG@sG)XqYcYxBBm6aq%2m?-axE%VhWI{;ypU(`gO;3k84fMj2 z9=JIaf@$*|y2(C@h?OP%wUhJg%{^Y$-bvACW9r=an_-H`T*wG3-f!Zr$(mBfs7gPH zm6@+MAGIuG$)Z>EGt;-o91_bi}Yt=b36q~gCV>B@6; zwhRclMd4@gZ<`ut41!2=hJBW7RN_8#ACa)DM&IH!joO^r8CA$CN;K*I972-9;WSct z+>m5WF{k$#q^ERahX~)wvTQH=3mJN~Enl>AkUW*^CBN3ZVL&^i4Pg`VmrMHkowWsO zOfok71MtzZC-F&OGpgGy=><{k;=){B$MyirJ*_FJV68`qR{h!5mWAZkL&D@B;+It1w|KG1PKdt8*hdy zl^lCaoZH}I)6`<}8?PTsH*BnjHe{Ih`p+dK@%z=hVk7 zFN7Q4Zzh}2KN8r&NZ+JcY|9T##ChdapSw4AJhICr>A zFMG{GqT?#*faGj^Ol%xXW0tMOl*ax3HNSG4^EQOko!&0ew)2H@irFPQTK$8H*m-ct zP(K2>OOT}cO5^VEGY5t~T#_PR{s_MOg{fu6u?fC7rsmwaT$a2pqKV68@~fMv)?3|KG&aJ?=TB zO+Sf!Xi?9j|HcG|X-V{xRZ+u-yy20);)aVK;2(SExwJ20+{GY4@# z=2P~w=a4fb6(WGcC&|J^=NokNjr!(8wzkun=llDYrI)U?COtO4D}7UtQ6v|U5x2-G z6ne}S@L6{LN<-xqhN#_&U%sjv%mI{c@kEV(8bE&8J1b1zco4rnTHs<}*=QP^%1T}2 z6_nCcBCy$#221q=;Z~H4bs>rrMbj@Akb;7_7BK+`8FJ9TXqQYW^MyT)mDks@{hqm$bbp-2q>iD1ns$dZrdP_69+ zvF@v-muLVdefGqWBjHMudAGX7a`3VMc@72lGBi`3_NB&8r;L}-C~NTO+#H>WmjFdt zYW=Nh12q{!q1{7yK5t~fnQqJ`q?}OD2v=BwUN%|mZQDa*Ob*PaET~SvbeSC|p*Jm$ zM(!5z7j&eXl`3b}`QGVypbvz{-L;nnwJ|u%)*`aw{g0}yLb@m|*XA;(CvSr}Zn!Q{ zn9o9gEMRMp5A4RN%An+*u{z-jW3N8^AoXeFu6b(tz;+#!;*~9aqXGH*^NEQ% z^4lUg!^9}r0ww>~*^fjvne$*cIx~8b`>DxbzGRkEFTLBkSGE|I+PUblU67g`SEwXf zdb`rmFZnKViZVL?48hIBEL!gP>hQ>wb(v?t@mb?7tw$Vx=HmB&?VTqo(29SoT#vj) zv*G%=b4d6&kj+w2N^H~lA)BO{lj1d&{D;dZ6 z<$QEHYIGY0-_FO4m|w4rHjrd6iL1W{*1X{E#Y?gaJYyKHwYbuoeV<``>h{F1KC8Z3 zF*lQB<`W=tpHQlFS~vc&s5D=vvu@5|l+V(6HiP^ole*+1NaDwd>W9-2R!68I~@T#YQ71GkNO)GWutSLtg9CRr^6JPc}zg zw-hZI+QfYNnO^0r2~zK%^Kk2{k&zJ{TT@I*iT>REZW6|%!f!uzd>D5GT%w}CRpG!4;T^d@Uk+ zO}xGu6A(u9o&!TavF$4Dte`_=E+f37K-57o-t?McQ@e>)8dw-5YsvIpz>2`4fPb() zT>Qa$MSRgHOto|Qj{O96RD}EM?K@h+qt6xqmB)88+@@)pB_B~h3^GiBFLyD%9dSwH z6t=qCk#`oAlxog)MXzI&8V6gz`>f%^k&0aRa=Pr`+Qgt zArK;e0*yAVxZJD|*Zp=$ve^k4^#N^Fu^Tje((E;<(K1NU3Pyak?UjUnirlI^FVBdX z<8Pr&H0gVnCG5EqeKF?S6o}25OH=(}Z^Xqu&9;C1RWr_4z59iTNvCD z9D)w6!QFN6z~HVygS&^|8a%kW1&3hoZ|V`Me&0=dhg!dtJhjRY+Bndp1~ja zVeL9K4XrYGHu$Rl+Gmw1Qw0xF!ra?iNK;b4j*l4ayGKcXyX>rTQK&KLEh^e|5dWlU zT%*mJd|&ehegJ9!a=+PLJL5K+&p4` zBpkeiO1T6;57vyWnnC)t=}lbS#HRRO5-V3ivor@F5MRAjic^5c?C@iXvA{Tygbg-C zemN8i16U$uhG&JJFcj3$nlVm#<-6=P{e_+x%3Gi3RZJ7O#W)}^C921E*D1!|%!nE! zUxe8Gi5+}#a92U2xazS zWPKN#j589y=`d?8ln5kspVWTLmiwwijeign^?9r_N1o$Nz!cKqwtG?jCDi^DGFZ7fn2u{eBnZuu`j zp4I;Xk#?PtK7Yq0!YhAyxZSp#B;&JYHIyZiwS! zN2;+GF(%DYm}@yI>h-FKT@v=ppJgSABcMr?$T1m%o|xtl7l}=Fwwssf!`Fi?L@H4D zRPd0j{I%TXE1GMbpt+#3);vot3odbt-g^F2p7DASgK`FrftjP_nj&Ks^RMA>#JC!5IT$ITRo3VI--le9`Yf8`Nf z0;Cd12O5Di7LS^pZfY%m)^faOoVD~A#W*WJ$#KeNv&haIjMcy@znoOGZqNl)0XX`h zL|?fOiO;S3>0WI$lrSa>FiI&0(bQJuIHrC5 z7K*RBuW%3*S^J7}ZCsT?$cEWz_|O4@iH8(mnB~*zLgw(8n%gT>j(Bx91%4gu6`1ic zH8(1BnZ7 zY@)5BGA28kh@-UCkA=brpYq>%Wc_J{qS7Z{xbd8-zBeO-?MoOicxF-2F^@g%(P3)K z-UAd|bHHjt?AJef|9B4#jkjMpE-2d3s5)?M-v;QT=CY`g2KG+>1n2t5-v)P>TP4hd z(WRF?yleECPx*H918W)4lbq^RRe0+>3=I09OafM2g=4yQrtQJ{7i}A+2(lcJ&9u@J znL3`^w{HR*Fj`;FpsVU8OE}af9x60aTx9&J<&c$(Q1mkjdyle&a;=*G7egt~WdqE` znI7eR6gz?}lgDD`ge!Wmx=rJRFwduzdWtlXz+Qad=^ljkW`t zVj!PYOr1R?WFH@4IQuHfG!wS-eCe^SvF(~xgR*7OrIasK$Qhj9!%X2tzNe*Nm$T{z zOU$;dC~?G3QVk}YTDKY9M>5}IgYd+wvaCu+893*nekywyRWnR3p)HgxTHhWV`p|-m zi?FBlabo1F4u<}ukbU-ex>cZf;^)9@%tV7|5Bam^mh7Ji2E9mo88zXBcj|5W5&7fX z>bDB(uJr~!GdF4EO##qLmPvQ^dl9I-`J-{#UDAFouW`7lZ~L9=HJqvJG8Vne(8rLG z(h`3$ov`wg!Ysx4?6o|$tZUQpODNg^!bh)o-5bri2e$~8F)$G*4EC)@_f!@7*b|ZNGwmhKvPDMusw0ufCzc)j_%J%u01hlIO;b`2HJBT3sjUUqoANj?Y#f z!lwUDW1lcZ4Ys{!6>SIzZ#?1sq3--R?|<=UzVpEs$gy=3*Kv~Le;2?>&b=$PIzI)! z>&h<2^Ahl~Wm5ttIyGyJ`@s+7_%>z+FCdqT!}#-DC((W_&nc$@>g91$o{jaNCL>5h zF)dNm{EDnM)mvrRt9RD-X6pP>o3$ZZV~BV-)MWGU^U<6iw#w-EYNIfCI#7YfDrkYiK!}*6Zm@MUeuf-lsiPKaHq^svT{rsY+fl zrz`hdl@Y|dYJjy9-$Aoo0Vlo`@>FRM(-r&4>EY>Wm(9dXd|x>zVaT+6HhnHVjeLL_ zG$FGbb55EY906)-y%sb6|+Q+)rqg&$=GX>HD1b0seUnwVk#e*y0bA zZ~p??x9FnYf1&iO-Hf;Jg(r@voj?D*oIqoFm5fQlIV3A5xqDX?90nm4guvV>FcuZA z7J$Zh==cH6wW6r89RtL`wv9uTj~DuZx$QdkbC#>W>Mb=jBGGUlkt~LOe1}-_={1gI zNNWvBORxMUGi~zbmbopHdl$HHBeI9Wy;6aM6wN!4*cxpVYvX}kVA~yuDv7=c&_y_m zMAh&K48vjDA@|z+bh_}X9!#xjR7~AyDK3fet&ye;<6dKItC2WY97^SIxXVlb9?$`QD`PQzV8Tpai}=4Caig%;gC?^DQ4_SKre@%AwXb6o(%*t1LG4v{%Fjr{u)pd ztp2hQ`hNTCJSHAuz}iR$>4qx;M?r?!X1rG!v=X-gf8|bprY3f!9V&Z(mbQo$@#;fs zvJZ96B}b2_dnIhxzVs9cIbTZ4YEdMSadu+mNCLSD&ptK?7|$Q?Dtn)(lU`2@0y%LR zQ98?n^oSgd%_yt8&rh(og)B!$zq}4eO;zIO?zC$oA|B2OG16mhqXN;JMtpAP7q>@P zC>ml8QTu4MU+qb-?e=lv^_>XdqmSxM9DNZpoMBzV#5zC}|6Yntj!9@x^g2i+t;Q~I zQ#PAqqk8!U3qOw$^gSkpb0sSN;1B-oP2n_Cv0g+RYmnKlBEA0ixpat)%QH0vr9{Do|n5z_HA%?8W%y#+`2k$R74;#DQVl^ z!}2YDgL=!!cYfzlreVc7ev6J|6PMuahqagIhIs>T_#o=>*g|Zn&LZxp)tO>Ee8EBW zh2^E|i`MWCso3N0+UKr)mM+EToo%oC->jiKF*e@*9lmEJ)6VMlqyL&;NFVdRtRaO4 zEJ7)LNm5*HPh*=Exi$>yW_PXH>tTk@33Y`#_?+TIlgnd+nu5@J#+Ri)$o#~o{IfW6 z=R*rW#A-ILK&PS#XU%5g9mXHHeN;q7r0*4@NArIQE=Wg#BzTx$agp+PvSr@7PiCKi zNn5dMLuYhfBcfDS(MCmTT^Upt>@`2{&&d zLA^>$^TAMl4G1;CJR_1SrKtTo>=>GmJR&Ex+Kz<4;h3;6b9cpHy*nxQ8iZ3o??#RF znLKFFvTPP0+sSaK!`zGG;$Adm1&?R!)yMoLC0`+~nWQ2Cd5?E zp~rvbPxreS@DMJS!+oy8NbP^OdtPRSFY$_A|7!FtI)-fDKVXacF4q2dPr2L={5>qFe5kUN}_|K#*qND8{(;VY|jl(29JI%POya>ooSnw6Pc|pY4C)sR? z`Eckyh>a5%Wz;HnnVG6REOgYRWrfziG$$DYiI5&15pH0{RhAci1vkBr^I~A|ZuCo2 z{!6Olo^3xW{0?N~0LkLodzDd{1_DrlV75RS+=5ohn01L>Mm+Mn+?hKXD(ln+GN)0# zm)Ter=tu(pO1k!@Zn;p;tKD@6vrJ|@4mPDAFpd;i=;mL?2?*@yHIQm$#gDlNO9ESvs=uC*HKdZYb#K%- z!URB{geTS(&Zp|x`5Lev{(t?75K$JM;?!@nAk1wC(~$0vSQ70_{2IQ0u2OL z0ejt#82a6=xbwMw-JZOC`X_QXd^*(n(suH`Xl6Kxh0#q>SZeP05QEO-c4fh7Ijphh0vM&3jpqa0Z4?#FZzizhC(r*>giK5>JIiFN{?SV zPVo>sZJ%iE{Tvct&`01yM;Meam(@k0<5XR+>Te9thhPsE9l!a7uvjZde9_vSt*E7d zDxP>~({@Cm#c>@yBQ5cD5f%JS+?GD5gcha}fU2B55_26Gvut5Clv1E#a^mC0JarGiI_z! z1HBL5sZM?R@MWd-`rljiPsqeS4xforA|n7-{9m5ndfeiFqHU4qyRCwP0{{CIe*po3 z`=WQg2XG>a*WE_==eNo5_O^Uh6z(h#W=w?qgLhtUIyr6X!Y6Wy@_tXq{yuo#E0WT~ zdf@ujE=7+rpsUqmEVSHP)1~P7ez?+_wd%3WTR{RNAmT52nurE&BXc?&$00-eAFT6^ zxn#rQO`4BhiaP+@n|QiMmqIn`?*T&_98BaSv0iq?8GIdDBJQ6r zOjL*wl|Opr8V8MYzzGH)Xg>tVa@gK1GXZv#H#@bUl@RN;-XduQ1}k=|Pdu?PDdu$< zZWozBoJ@9h9<|%+?oBZ_g7Eg^&x3gPwu#r73V=o$hDBlpPB!Ty3$^6;&_tcZt51bF z<+b*LKs5rnz7u)fvvTZl^{znNJdQ$LrAI}c>fvp#^sXT@{RyG6)gedW?|bIHS8oS1 zp0bo}dUc_jt2?#1qD^xLH`S>tYM%3z`e*(x53n`(2+{UE3n=G))FPh|?vke}?+aJe z^Yt?p!PS3eDf)8@3ux;T&&Ly9US0{rm2ff<+@X%WG@1qfP3#uW-WMTIIpy(;*xhf! zJ|g*@UrivZ#;3`uW%eDkUg|_7O`iJLL>kcRgqF?e_Cqt2!`HsTRR!=E2~+-GVJfj= z@#oKq`R05?2CE__A_wWs?+}@bvB@E)vytWpRv?Yq(&!&w^ulT!v#{nP7qu0dPe4JA z0&H3->B6bw+m8e3rbM#wS^@Kt%&*Ys-XH)uQ3MGgD9Qx(2-iaQG1;?V=(o?G#OuLk zJiR3a0@&m1(+6~nV(6%f$Pv^5mAE{cwM=3xKeU}t5faMioTk+IL)Cm`{FWtU0FYBX zhMg?c6--XtSend=fmpw$Z53d>!YAeAb}Vsl(dV)f)`3hN!ffcF>t_^ca5TUu1*&_r z08JqLi)tbZ%>G?uT}1qORy`tBsgqCFO}8Ac_9)?qi;}ebqDg&k+SUb z=4!LcqFXIcs+B+&6q1*5`Av%14*V5`Vl7lz-*@ZVDJ0X4i^OsN-i9=k&n8xIU`QCp zQ{-?)Y_qbiTlptW$ScJNPMJ+q1?B}*o?}_)kI3H$+J}CW%S5HQsvoVKxC<*$P9_ zO^6D+%TAvpu4(2-F@isbHH^wdsz;{i^(d^08=%NaU?^hXNp7}!zTsR(+)>Uyc5=Ej zD(-t<#CA&k(MfeEXWO*&ys(B)cRUJa8qlB9-yimX*1(#YK!Y2M&p8kKM3MsRb>OmI zpku7C*ZbW{Y4uZ3^U#fBh!WzYx_tZQR1Et#p9w&Bz!noc7nhK!bu^;oFF`t?qg0iN z&zlkw_V8sIiJ!L{{@fxdkML`LNt!MWC5Fb3>}ra(@-VWdn(h*T?>`Jh`XQy zhQl+q=)M$oIs>|gK6-JQC9CdPT_>3)|2T70^#z4N;}0dv48PxtLsS(?{VpzyRM>pc z-zrOS&|^C5R_Bl62w0u^67Oiz5;S0Tjc=1>d5L^NIv1SXL^p<@uD|Uanjj}BO(gLW z>8(E?8B4>vT~-%c#xi(pZ8eF4QFocn@Yqa+_JVF#J7SmHMkPyyVb1ACUbRywf~+IZtAAH-Ph^Vg*eVBrds)?hd} z4YGfKXr!Izo?U8GC@Vh4nZ1U0U{y9= zQ5ssYoOM%-vfbcTCK(CpS!aBM`4x&Au8%^ko}r2-)S{WWY97t3q>6uNt95;a4)#Q6aR7fJLzcM6 zM7_rkYmI18;t89xMFyX*Goe>L(Mg4&3m>4TWBaTlfiB?gH9SM6o|T+3dkex00)`#F zPYM3*KFXMj|C;dW|2~4YRRoUtw$CW=YdtCmg*>b=@8oCtaL+N)1YS)&rcn1yu%ff& z#;?au)0rdL-hSyeOL{O$gG{&e@^B=*rJvfDna&ipSv>P;y0LG+j|GFNsQRGT*55$+ zsp)S8u{goOMqm&dw?@}JAx^lp4+`OJqoRGY?uPYjA$r5Kfm!Szq_GGQiaYZ5J1_rBGT0sdXme?NK}a1{U)qyOZ2R%=4Ib!Hq! z0<83!MaC8$EGRM2xWG5Ko<%s7%Q%>o*@j_I6LP6fWTYU7c|Ps;*ZryTMS7*`m-m^f zWqR3V>SaTBRH(ue?)psv5e2_eicf7v+ClgOv>*feQyuz=KoZFH+kgLvHWp3UKhM1X zxBwyl0G5Bh7JipD89e}P#)5sjUmAX6I-zbiuRaFlAC1Hzr55KDiWavCx9}Ij1tZA3 z-74Jpi=2Ovx_HZOT(Cl_Nq+wlC=lzFV5cm)wh49(b>aLbDOOoCh!;$L=|EF zzYmrMJ1|!azbrRFK~S6-73Xvv+o5u605QIH#IZs6Aaa5>3{8?K>9J);(KJd*Bm)99 z=x6r1^;k&wg4GQY%jw;mteORALJ_ov>DLI^#aJl#b#{MQE@~+|=uZh^1vS@%9^#_o z0g+V8XQA52aJ7UhsFtx$$x{lMI7bt^v3Qvlja8ZXP(Lh~Z3~r39Jv37Eb+fc))@W2 zGxZ%6{L``?b=<20v8k|ITG$bRzX3>$DO z8=!=W7`F2iUC8bXO{jjGeuv$*fxqW9+H1gcg2#!^`Hp9mA#j$<|%Lj_Ex>#)9%e5;YCkXQ) z29PD(bzejR0^fVFBZNs64?j$|Q6hH_hi?5;iqF`zCEW9#jl9l0dv78Hlri0$BUft8 zZPVIcElLmq z={b#xyi$aK9-Pky32(u&a zC0d2@EEImSMtuI4(6kK#pggAr3UCR6x%vzVLL}{Z78bqC^X2qLuV&ZTXm*MpxTtmM zP`t#ftRQ(G1>VX@7^6dK=18tX6sh;R{9 z5xTig6Y(0IvE8?nN>mL6uE#?5GhSs9KY|10ao3*;FsH-$Voy$(2!KAbRxhi=_Uw=Q+V@4MpmJRFWJza%N`QJ_O{6US#32~uOwm(FV zQ3rAY#3NIs4m)${%QSD6DY3+8>EC|}?eM8BkfnhteFM>#%vHcz(`jmmRtREXWkN2I zGjYGQrPUWTR<5I(+r^qLl<2Wi$w9?W%AMdoa=fm;POf_s37gP^6=T5bZsoGGq!;d4 z_g3+;wo{7x36+S5B}{(O;E3heo#3h`6p&0WndzUa{@pl$`0=i$fu6Q^DC|J!^qEE` zP#h2eOu`{BMudj-rgjcu66a>ib55vZwlK#F9M0DTm7uzX{* zP>htzXsP2vRgY;%E8oINgN_vJY=%V807ex@N6RdXcx$-&dR@(SWAO(VV-JT`bU!%x z-l$T&M?@kJlPMLRdNu5());lsk;vSw1Z3eT7P<5$J*E89>i6d;=^-+WV;6jzR^Y=V zpawl6nwY$ISx!Uq8F1ODKHp}K?{^m{$m0d(ubTCbkp1Wr#wRncFBeM=c7o$^WM_2k z4vZ)5WBOhkSDvl5bBLENZj&thg|jX`Y-8K*i@m4`rv7$4O2Wy4_iDRuik~RNAw0Ac9ednc*iR+{ z>1zkNFMc-{)5TOEM>;a!`u442=La}01eHnZYV>{z7Tcl;dbjn^BcI3qE&-3&!fDE*^RAO$IU}0! zgJ7(Unxh#gc6g@Zk8zP+j-Co~KNQFbD%4}dHi&!_-f7)VE1-|EY@(Hr29a1=c0^6}za6eoopaD@8tqud4w z*fQ1-_)#214MfQoO({MKu*abdxMQNbv+ry@dFlTDBqvKK6s`FNGl`+$U zgg!P(C}=}Q{o4}wEii)R=a0cJP;C@E+0L+VwT<5}d%rX+`kzB5KL%-VV6@+%sSd{x zD{Bu=c?zBVetBtqM)}q}Eh0qEyt08rATVlw_RLO4$KI0^{{?A05f!A*z(7knQ_-+? zyrez6oaTHc`{v_IZ*OnXlS$X3jXvqw!kux_=gi*$WM>-WAjHX%7%DOwpeDgwK|v_( z+n*s&NUxg1#UJlnf>nobsCEdNk^mj2wqsfl(wW+y#q9MZK4ODiM2u&`oIBEPn~IV{QQvBzh8ugS-4q z9Y}NdI&n*|0yQ2SSgAzRF%1nU$>$C*1%6{Xr1^^I^f9{{WhdeyL`{d+g<2ygf3qsJ zn^tc^)wd!AWsb6!G!K?W@T!>rw+pMR1S2cddFcVdye_i4)UZ zdJwm}qlN;BAPjBCLF3$OO2h6#%4Nro0szo3d)KcsF&b_c_L-XM6Mkkefb&OhZ}5vz zkLsGI|Ar|Fl~2!7Eh-pCCBK!D-V*RX`)ljmPlOH57!v&-X3hFe51O_7Rm=k`M#Ny@-dUYAnfUT_ z%eC^@??5JV*@*v{2Y}N;&fuQ)givMU%5h`L-+~DeWQ<*PSHiU_RWv-^=A#Abb6gxv zK8VUFGZT4F9cYWXoNM8kXyeU)YkV@Yeu(?c;_{_^Sne7Z*xjO$fp|j(Qb1)2_-bRAW))*+KCktGw+qAlFS|y5x>K@Nh0H(R1jnuT^ndv7e7^8fC+u^! z9eehh>|ggK-1Fa~)YAvvv1>|cdeH_e=t+pQ(}QsF26lWM&vo=O0EHBjfyg-Iy*nKr zvnP$YV)n(g!c6#B95HbMOVn?FWpru8sV?w-V)FV!31ED;=f#$plc*CG`0C^+g(Zf@a22gji7RV z==g{>YQjtsZrMW~3#Rc7zZCr#l#xB3j7kKx>DMkaOLF6*TglYz| zNK;>0ZLhUmbEqhA&767TUI1`Vb6k^aJuB=6>ydqW$Z@2XAPS1uRbq437wuE3!ThOod0MZ^haV?;5k;Wd<0&fnUhJS`HlV zG`fshhK~2l_U}a9Ow<>ps4j#C;r>#;$ItYzaw)Wn1U1S=hORivhbs+j9~QqYN3Ov? z6gvR)t@h#cgs&4`84P{^2$YT{IKxPfLG)za!Uy-Ge#P^3<3m@PZfoQzw>4Tu1MXZM zj%H#mmO-FiDAvMA-CAI2$AkY(#D<|y*-?f2ZPp9ZjZoZHgPoI;R(Im33k1-CkL*%3R27HK#P(d+gED`ps=+?1 zdfzxUD1_5vDXMcDOARz^dseW}k*kt@MMXyY0G2suI56hSfFsz@>+saC#1W-O1$q{Oz9q z^WHzXCj$I?6v4;vtxybXZ2toV!zo=umr1FN_jb7PRH&VB3J=`HRDiJBeL(Vd`K7T= zGk86b-c2jmptC(f5*ibEHH;XPR7NWHYiZNDC8a2sfR z9=YS@qd!YC)i&P8>?mKebJ0+&_rCOxEAuAravvIb_75Pda=tpKiv9Ay2k^BK{d(g4 zDxjp@`8K&+iYK0A(IX~9hfKq5-X%|%^?!r0iFf~(^Ldbs7*54C?z8r!_xM16<73u76_IaZ zhmY6(@*`7Dv>j#S2R?7@6Ms$~9XA zh8E_}dwr}TINz~_EZ+?b>_r{;xP(1<&Bj2|;QNbuDykcOy{iBhOw^la-}jM_VW0T zBvuP20Kr`lSdxa}EK~H*p6%`JO!UFKjZy_f#QRVUkzfnk`<;Qd+8*U^?kfZSS7%-R zhYMXaG3VH8*QHUbo-nkva#cxppC|GW!NqaV(yi0@m%H7yjv+_qD*wIftqDJwdga)n z_E;$kzl$aRmok1LG1o1Botj;h8{6kEX;M{7H7E1fkAGmGQw7882T;O^FVmET&tB9Q zn)T(VDkjzrxR#yIIk*Z<@s@4ALm)%0{A$VFOc$&+1*ajkG+IB5D z*1`@)Syzx{on+dcWn<-C)o8A817Qd6DxOwGr?hZCsYRW)swiD&zuxRQ=sI()iePy_ zMRw~rRfE^z^9s+5B6m~G?SxcSHgu&koUJ^)%6=s#CvPE;Q9H#1TPgrcy>dU5ra-r4j97wIHjVP4{SMY71VB7u^KqhMm5@E?(A%t;Y7x>g}+BxBKfJ08QX zX&MlFOdx#_7AJ2|>7?{#AOKO7K1q^!LbABDp-RH|69lCI)dxanEs~S^9mvBb^^cwi zcY!#}bRKWHg-c@KtRr=1UT^e6uPUBf-3IGf_`a>mjvHM-_Fha(OvE633EPtmd`nZd z_vt-++g+o@1jW4To*n=p_CxDYMPAJO^79w}`t|})P~fy^<9)F|-;4+~Xol!zn9uNX z>+G?qClRp-{Xcq@iB1x%iwi|_lS2FCKcDhq{VF{^v>qy zto@}zO_;{~tgZ|H{|cg?|5p%wK*p`RsAFNLz4<)`D1;}rw}i7W9_UJsQ&qJdRZKwX zX{g-rrT4t2h|Molq?N~nnXBr*S2GmzK%jT%=&zRV@jeM;3H*_@@@C1MTmpKv| z@PnA}<-%2H!UQRP&AUH;-p2qdPt1j35$u9vyc22f(v7jzA8CgSMW-O)6~9U4wg(2&+ADJY3rX{E%Swfd1M^uOdPXH z8qh(@H{TvHB0L1&OGov7!cjk>6b5NVCKZrr3D{3fpk8CUKq^MS;7@NI$809*_Q~|WyUk#DD)s<3;)T!ZQuKMMBv0kKKtdnhALmXo8(<1bLE?u{Cyd*zmtSr zcYWJ3Yqyv8kNF4CY7nD+ zmo8)6VfJC@2*q;dU9A0+SLKVD-}TASyhqm@XM zA;A#c=UhtvA>gPi^8fEh1mV1Oe@pqKmCE0+U(^CKSSqoh;xUb;ecA?W;40nvf1lvn zns+|qHZfYN2CG`FtyVPf?;4W=S;*4!C5Poe6L{?CrpjO%GD!|Z;8#Qt4vMy8dX-sNixGg>qF-~J@~iqBZbBZscd~XL0A8omsLp<}<0y9mwj*fpEy z!gi$)w#zkqwy@LN`@i(yv`MY2xIz)69YIS_Cwn0!KI9G&S9I2P=14FXFVcrVs%(;= zVf$%P>|kWX^7mQPjutq<=1wS|wxGxbOEA$A#7h>IdbVYJFoBCt*O}h%)N{6WNHbb9kqmnsvwnPyj__eKdM#)1!S4N~c~$+H z%mRXDcH*r92il}-pDYU3dB%+#>K$D*=3r3mF;@3cC_hIf5j{iUX!TZ-2re)XM^>XP zEGU}~RB~XAW-O(pfUBu!$9zxIz@)~Omdk1^6%;vFGZ&`qESh7`PS6`^mB&5~x9dQp ztT#=B4$gi5EOt{97iv5>3a>_9c=nPr>{}1uq`9+3yCt`?)vOW>72)&Vlcu%jAr(Ka z*sYZ|_b*G0N?_}T#ebU_I52{#ns?J}qB+M*8bl}Fb*LBp^?P{v>jlz?wtu*NiqpTX zoj$@X&M}5ucxi)ATMvS(TCQHqBE?IEzC3Of>B!keCwK;(w>62KZ>h1jr^(tqFAzRi z%P}zsBoFlE_}orR_@59;rLZI|{%rC!)u}pOJ>R#g`dj{E8nedx1-nXaKtj9o<(zFz z&;18qZwMaElFaRs$s#vQsLN3FK&kv)yFkV9%G2o6rJl$?w2G7Z;VkjrsQP1SiJsrh zz-*QnA)aI}8!KCdx6JvZe@vv1oafV?KW?EobmXD9(O2yEi^FDJywAx^tJaQI7hPp^ z^ooBBd_wO6TdK*geNqNW4vF2lGQ7Rk109pdtJ@i2>WH(WP5To8AQ=c;C{kXAyv3e4E($^0RAd;Xt%Hovu8V?x}x6jr^Q z-45^b9B+)#qDoFf&?GRFC3qB@e_^o>p}oiSByfNfuj=nJV#Lv~CH>|R;uKO8 zP?5liQa4eHP-2=EyhncNW8G1 zQ$9Pq<5Vk>GvS?0i2P$_M!mM^BV%(VDY8VV`h{>@TKTNCp&=y@zIt%hnimkeh5zOi zh&f5#n25erPDUnti+{`?TkKrumN(~&;{B4F6jN1aZlJvZ*E-L9qq8Avcdb+dM!atfeB6_4xSTJRGBy2hwpjHR52*RhSvZB6 zSNPr*RUG+gWqecgb+w71upl)!;llFTNsH5pCQc0RRo)#%L~bz*rFAxXtJ42r+~lHF z2M%+`8#(_Q^6nt_fOks2H^n|(T*7f{2v5z$gJHtw`)^Zte6u%p)C|+^t1ncVAV^Te zMdOl!iXRF9iig%cgb9W%O)I=tvU|#&r1lojCx;IsuyJ6^sJ6RWZTHmrf_k_)*&hosn97n%c8Sb$=H*9 z(wK?JS5ML7SA1e>ZT5Z>2$>#i_eIj+T*9UPLS9WOo=7kR{1ug+iJ4kFP@fU|I4Coj zJoe6kWP|^A@Mr!_Pkc#W;bemjdOfhO5D;#3DB^NR6hA0iYZ=aT#IBx0me;(0(k3kz7i=3J3M0Pk^qy>plALD166a4abvUVnw(6J`Ypj%8mhI6p(zLLL zW+iLA6OP!g6HL6(^P8wzQHq5}Gcdb{5n7y#+;TV-yQOUQSXGpT-~Bt7!v9UtoG6TY zB zo>FQiIY=rNdTk*Gg=S@Zm2qh$rv!Zaxn#;`<)YqSthaUbDJ}32s5Vf$7zE;BpEs}# zC=^A>l||K7yuWC6#=G_k4p;bky8R?Ix;jUBapriAI;`{`wDUwK#Z>isp|f}N#CIn+A5`Ix2&LC=wJBoDI(Wd{8jZ*KTXbBOEiY4kxFtDWOZ7ueBOK}rJnwUnTlgeMW$)xCVz|%c2L*6 z9Xvn7aPE$MjZY)XZYs-tb><~M!T$M{P2ao z`226n++uMQr@W&bRi@0&U;lS>|)~SaAd+ zXCiK1q}^acdNGj)fQsUL#vdFqud3u&Wu2`9>l2wl)Iff!S7~AM@K`DzDsGd%hce@c zJ_d4+H#s2(RUUTAkyD{@810r>#h<3?e|4Q%@N&>4THs$-et~W>Z}A8za`Kls+*eP= zpSCbB|FPHq-LITiK+rJIzyrLI=^5+Vn|KWBv8vwPr4=HUzkTO+6oR%K@sR&$)=z5S zC9C??<~nJo`}Rq?Ah5+ge($oT4QnLynQ^oR-k4ydspX}jfDLNmNjph!2x1Qy+ zaz%u^PnRtiVS*oWk?Vbi2va@E&lS3;XfmxjxLD#nWAPUV0Sjz)$1R)6bJT!@%3&;q z=>&O!1>k{sM9D~IjDxv~x_~Tl0V#MYq+w3sgM63Fj@FR6imILajde7;*yURg1W%Ij z>(_(ltKk&>1z-TFj6cczK;|+}&XD`_1f9Y$B^aD*rjwO}Hfj`OL`H^$oMuONojHdg zx9>BV>5c)U8>Cyh2as-*ZcrqYdy;Fw##;VY zod^A;1nd~GI$-sVXO^0dWoJ< z=tt1pS#^=`kA_cicbl~}y3yh+oB>^kjkzD|5_>3koC(py*J|_TN>VAi?E2-zT!w>9lGIj5b3P4-{eGLUbwL$Pr z;G?(ZyU5Q{vp5BuFJ{06NCkc6)xxAH9!G~%e1R&uE-u@2fQFS68qbCJOlDSD78Eo9&d9ro8M6HjqShWKPvd1xI^C! zmmrQ|R4aR1{+#6WB!FFL0M|8Y1`L_f!j6ga zDgNN=rK6tDcrN=<))T>PEczewZbw+M_@>U*c%AIt)b*HiE&Us11~IZNHB>Sv#vvLh z;{J8I0Au*Zg^@wW-I{~5lt3px3k9WGBuqjwQ3V$?`+x@mM}i@VDqsSrY(ttwe^_DC zaX7lWFD7LM(+;~|mb#AoG!n~)~p~?qp#i#n9VJ0T|0a#9$T1nAgDSR zJduI|GWtG1Da2odBp}VPG>YzWi#z$_;_#d+$VTIu5*9Rt$=4$-@BrbtMCmJC)pOjI zNikxCtx8Iw*GDSo%wJAzFZ7+qeElSatrSx&QXmt#d z(Bg$--}srSOWLXmG<~+*>9VHC|3Lz^o1};5_PNWe4*)!!F!PD+d#mlL5=@nVj~P6# z>rhj7HlA>Y-xF^p-3gwEkzN!3@d046@lJI|#wB%qmr^)R8fXcln9#1~maVJfu!Ug# z0U8bO?xPhM>4zUX4mJfzhKw3*PQPl3WUB+^eC}6ns_!X#JUds0$K?*tyS6Q~{{*KU znJiPZu<)L5!VlBR;PK}^ZTEDeXN-n7E58P_lJ2mrl;R9_unen6hkUAA)cH&9HYy-O zV2jC5>*kp=OmDU?jhFrJ!_%?q^DN3n18~cFnD^^Ejs~6c4B}z#p158`mqp}f5l2HH z)^5@kmd;HfwX&IZRp>_{4(IHcKy1LSC7v!sph&TE;QEfOL4;3A!<}a**p}&PEPJY0cpa zn_J`M!sKswg+G!B07T!!JrY$aGgiDzY-C}+66SoB`8F>VgOt~hJ8n7DMTak)aYLE? zXczB{fL?MQBP`M^>cwZ7rC$tFasjs3%mm3Bg|F(nRSaC2l*3~BV0ymOkqItoLyP|? zrDI)u|F9c>n>S_FeOw>H%1b9K)>DXpw~*oWNE^$Mglq?9KpMEs^h!W}2sVa29Myzqt-OFL8D8Vw)J5~lIUpiKKJ9DL5-y(j zLwD@oX;eHS#B+NDR?nf~p!tw*Y&uWL7$V91FvEg53a2CMdVJQ)%0W58sh`S3y8Q{sU1=z~GnDX>Fo!tDr4l zvOBDvokx<4S%jw7nI-SJ=NoME_vQwq&yHI54xMiO!Bk!c=W@63L5kRLWBM^=I`V~9 zL980_)BI*76j|80@-zP(GbLhb&&1@a&L>(q;TZA%wyLvtk#vTWJ*$W7W}BtnJeeO~ zOuK(N%D#JlD)6^Gyf(5zHYk!l)FpdUCPjZ~3aLeeFT;C10|^((r>e6~F?mdL;B~)Z zQJ$OFNYUtGkuInk?vZ;Dk`a&!nek4`b_rPA6Z)3OiMU_DU+anNeT?RD^KG0@va7z^x%odGXil`Ep5U%1-dWPfrE%< zC?cF8is?%lrO6M?duoYnSr9aQvIR)7#ruFdTc!1k;eM67HYwJwMM3F|2#vjn`jj<@SZ{Y1Ru)y2yi{+4L_m6E` zi6TT`FG#AQOrxD%k|!??{*`cc0zR6)W{0%j~=nU*PTprGahS?s*6{A4w{ZH=~FE% zt&r(=qqlkS=a`gzy*4-6vscRl)9!7oPMASi=(Q3@V~^n1as_!=3s#byzsVCquD%8=8h2 z87sKcfA@J_E_3@^g{bc0%{;dQ$H%aAB4?~hq_CCX7G$H$!NM#Cv6x*VR|0LEupz&G z6aN8b%?~fLhEbBp7DDw4XK@>0vL#Na=qaG3mnQp8Y75veYGg|r7W^CGmB8404uNx# z<};T7j=$Vek{6v3tG%E?6o(YEgj3xpimHaTYN&!XR|az^!Pw6U^fiJQ)&4nJnK7a5 zWzK%ZVT~^j*)YSd#vJdr>KBf!P0o`fD6KyCTAVZ9H5U8*IA)7xZB2Z zCiowBT+sD@YZ1T-|Mhp`6r6yNh2|=+V^xq`(JiznKclmEe9X<*fP%jVUYSW%m}I;~ zFkcs`D@(_~KlD1ur)%fZ&RL$cU^LI^kQjfiM?vx?IITX?)C_+{fkdIVEJo@*FKn)NhQ8q8NSe6ymTZ&rNNUWmJTz-k+S-= z(g$Slo!a}Kb%V9CiYg9AqL_)J?1Cct-C=@O zUO#Rw7Vp<3=GN)tLdWE)L6efYH0QQl;fpNc-8p?U6I0N}DdV78q#cn`gB^R%K;#3l z)Jl;1)n7o#cvDTCB68U|?VkRFr@WcvH3{jwl@@&zR#ubT{@B~boDUusw6LHIH^s0a z+=Vm(5a}$v00aGBiC;f$sqSgXTbw=z-BTI1CF0zR90-Y%r3zxdb9R>NPb$B1(`bRs z!CZQghg-CapVp~QJC*+t=3&o(=}TUd7hAc=Wj)$=ms399kq)dEzD_ebq~DDU-3O9q zXtGz4chsajpD|)A((bCllq$Nt@ z_(w9|W8cK;&^kBM`;MD(@z^;~2Lt0v8%T3;Z78IA7s!>5ew+-F(R@R4f&s+IcZ zCWdeLer?4isL<5hE4UulmJK~H%Qk+n*G@I!?7Czm_ zz(@{4HrFcnJa%KdAf=hO`H{uPmz!~-lD9R+`@|gn)!M6q$=_a2I70IOa0GNX z9iI?2Qu^?|O1uSreWXsfU4GLTjm8pQ3OO|h5ycg!xAgJD)&3#?Q0!=oK&U&{oVwVb z`*Zb2V*_$`IZ~JgMF>c#Ni*v$13f?Ws-(f2Y2ho#&cWlxr4c~|PdGOqUH7KwChtsH z?7@cm9F^UJtB>;hrdcW+Kxt6&MdpUq#RVRQfP~~~e2e6+hZ+kmpIxWqKIB$?Q3R`Z z8hz(GNP}&9tBESuxk11kJ!9#L1|lR1!h-d`bB^1z>p4y4O$Yk1`0$S2F|H@hgh9MR z@8*q9I&V6a9HypoUJ=1L@Z9h#B|uE!E%PIM`gaV$yPWB`&NQNbE_Zu;G_6AU??BXL zSwqm4>9Ydbt5cgLPePvzNE=suFVB#o0K#jHw1rNu7NPsM4~ z;0Jy066xICk^DPeo7*R?x}(CSaWk+wsP97R=zLzmJn4ro1W;MMnV*e1g0wkI?mC^L z2ZUseJA5noYnajAfy^%e7#5RcytAz(1qtX8&XSV|DwJiSO;(;Y`iY^tcy&bQFpq%n8u>;| z=XgpIH^yniqHMPS=v~(=hbk|#HbwBCN2KKd1ABw6U@8ePbK?+2tXN&n3I&`;dMJ+27p#0(s`bEA29iTq}7)N8IR%9AWN~ZDIT+2_`GwIjO2ca@cP*^|LFMUs(GSd&7beKOYE2XzqZ!j z|Mh{5wSpOO+rK@4>rqv`jPJkQ2SDD(PDF7{d&ujnF*|<#AYp*p+WD$PyE8dtjLfwH zRIztyl>EB2$?xawyA?ePt6@e51?;j%W}88^<5h zv*l@lytE?{Aqb?E@*qL@lEp2MFEr?iQnS4 z#aoMFisb$ETUvKIsxV7hiro(0V5HrJ$z8JU8+l<1b5CNxImWfLP3RX04V1ij=_Gv# zdN&4jTrYg=%pA9i3a8unI(*m8?Rj zhwOXES0<05Zwai(Z52ugW3ju39rxDL7nLVm9j{80C!ov05_@1ot;fzpKyOOISk&STQDIJXATTfx?2H8QUQOS%@Y>tUG8Y@O?`SQe zEcRRooLE}gNYdPp&)tXvcy-z+rc$kjOi33r&Z;;Tg;-MFZSXBNuUac?fy zj)XsU+|8!Rzgh30qeG^AvGRQ*bv}G3H~r(bqmC?HWiU7aS7~{qMzzd{zAGvuSel+% zLb98~+)p~gy(X&o+C{_uwXZj zNrxap(-Z-tfxMe9Tn%y;^+nG;$(npN-A1oJEd1K}AfsqqP~X*A)nRZJM!Mz(ZF!D3 zE0oF^sTPNCElX^qhB(3dC#4-|io%t>i!kv6$90Q6IIY?vvr?rnDFq~flr}M^%(&8} zF@QGtP7oeaH9_qZ<{vp=040fYyW=a{>6dtEJ-^EQF%;mYSVG{29 zVCNkO0yd)(VQ1KL2jD`02bYGo6av-CXC3sJ-o=K;LE^nf?I8u;Tf<)wf1BAN=+DZK z{PUc8P1)6P88#=njB&f9KE+pe`&Dpb9`ojnPJVgmD$|A) zMp}oWfNqU-y_;)Uf^ARjifaPoa`F%;ZKC$K@qm{<5DPCsu1KbOywXcBWX9t@{0Qx< zZ4jdsqK*QK#8y<&q!5S1Gc}icj;uh6rkRZsNm^yRLvQlXOeAs3j^A%s)RV6Nz_AN= z>R7L;3`YEmef>%fMGNhke+Fj1y)4_P&}f@HTBGjBciJtIj~}hx@4WFHAV1r`3VEgg zPFJ*U{eIctYn|ONS_Lxl>!D>w&md{_-XZOR+bP-ZzD31oZprCSY$wPTp|g8s3JnA+ zxJCWi0Ou2bL8tfNvPFOZASHwKDi=~n@x~QpwFEyPr194-oZ$!23oU4>EiqFD%vOR}LVi+>hTvg{05Q zB^g1d4V&11L@qMdieYX9V2P8>W$^asUf>qIy$9r@duo|_iKg#$Jjs4N2|hj?uZ7>yO@b&H5k3 z_BQ&r-uhbOXFX9+wEtHwBf*C&eH5)O<@fv*z#yi#OwVGoSLOH;oa!ZukS+{whfubz+t#XV6|}USrT{V1rGlH!kD# zBW%M%7UbzTlHx`Z(k7d+$6w3-_`@Q?V!x$j>-S8l&6c}9(`On?^^P-78XbgUuLGnG zqJEK-h|58chNmsTdLTfnXUn!R zk~{`a);P4#I(S$r$dfK__N7D$aIz?;�=`IOYnAcXHXbSZS4Xl~T1iD722-nZL;h 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

s$1Sv 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 AA^-pY literal 0 HcmV?d00001 diff --git a/assets/charts.html b/assets/charts.html new file mode 100644 index 0000000..51bcf20 --- /dev/null +++ b/assets/charts.html @@ -0,0 +1,209 @@ + + + + + + + Twitch-Channel-Points-Miner-v2 + + + + + + + + + + + + + + + +

+ + +
+ +
+ +
+
+ +
+
+ + + + + +
+
+
+
+
+
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/assets/dark-theme.css b/assets/dark-theme.css new file mode 100644 index 0000000..c9687f2 --- /dev/null +++ b/assets/dark-theme.css @@ -0,0 +1,39 @@ +body, .dropdown *, .input { + background: #343E59; + color: #fff; +} +a { + color: #fff; +} +a:hover { + color: #f9826c; +} +.box { + background-color: #2B2D3E; +} +.tabs a { + border-bottom-color: #dbdbdb; + color: #fff; + border-bottom-style: none; +} +.tabs li.is-active a { + border-bottom-color: #f9826c; + color: #fff; + border-bottom-style: solid; +} +.tabs a:hover { + border-bottom-color: #dbdbdb; + color: #dbdbdb; + border-bottom-style: solid; +} +.tabs ul{ + margin-bottom: 5px; + border-bottom-style: none; +} +.checkbox:hover{ + color: #f9826c; +} +#log-content { + color: #fff; + background-color: #2B2D3E; +} \ No newline at end of file diff --git a/assets/prediction.png b/assets/prediction.png new file mode 100644 index 0000000000000000000000000000000000000000..c602e9a048a6454d7092b64fd3d16e82802c37e2 GIT binary patch literal 30993 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 Di3em# literal 0 HcmV?d00001 diff --git a/assets/script.js b/assets/script.js new file mode 100644 index 0000000..1abf0c0 --- /dev/null +++ b/assets/script.js @@ -0,0 +1,389 @@ +// https://apexcharts.com/javascript-chart-demos/line-charts/zoomable-timeseries/ +var options = { + series: [], + chart: { + type: 'area', + stacked: false, + height: 490, + zoom: { + type: 'x', + enabled: true, + autoScaleYaxis: true + }, + // background: '#2B2D3E', + foreColor: '#fff' + }, + dataLabels: { + enabled: false + }, + stroke: { + curve: 'smooth', + }, + markers: { + size: 0, + }, + title: { + text: 'Channel points (dates are displayed in UTC)', + align: 'left' + }, + colors: ["#f9826c"], + fill: { + type: 'gradient', + gradient: { + shadeIntensity: 1, + inverseColors: false, + opacityFrom: 0.5, + opacityTo: 0, + stops: [0, 90, 100] + }, + }, + yaxis: { + title: { + text: 'Channel points' + }, + }, + xaxis: { + type: 'datetime', + labels: { + datetimeUTC: false + } + }, + tooltip: { + theme: 'dark', + shared: false, + x: { + show: true, + format: 'HH:mm:ss dd MMM', + }, + custom: ({ + series, + seriesIndex, + dataPointIndex, + w + }) => { + return (`
    +
    ${w.globals.seriesNames[seriesIndex]}
    +
    +
    +
    + Points: ${series[seriesIndex][dataPointIndex]}
    + Reason: ${w.globals.seriesZ[seriesIndex][dataPointIndex] ? w.globals.seriesZ[seriesIndex][dataPointIndex] : ''} +
    +
    +
    +
    `) + } + }, + noData: { + text: 'Loading...' + } +}; + +var chart = new ApexCharts(document.querySelector("#chart"), options); +var currentStreamer = null; +var annotations = []; + +var streamersList = []; +var sortBy = "Name ascending"; +var sortField = 'name'; + +var startDate = new Date(); +startDate.setDate(startDate.getDate() - daysAgo); +var endDate = new Date(); + +$(document).ready(function () { + // Variable to keep track of whether log checkbox is checked + var isLogCheckboxChecked = $('#log').prop('checked'); + + // Variable to keep track of whether auto-update log is active + var autoUpdateLog = true; + + // Variable to keep track of the last received log index + var lastReceivedLogIndex = 0; + + $('#auto-update-log').click(() => { + autoUpdateLog = !autoUpdateLog; + $('#auto-update-log').text(autoUpdateLog ? '⏸️' : '▢️'); + + if (autoUpdateLog) { + getLog(); + } + }); + + // Function to get the full log content + function getLog() { + if (isLogCheckboxChecked) { + $.get(`/log?lastIndex=${lastReceivedLogIndex}`, function (data) { + // Process and display the new log entries received + $("#log-content").append(data); + // Scroll to the bottom of the log content + $("#log-content").scrollTop($("#log-content")[0].scrollHeight); + + // Update the last received log index + lastReceivedLogIndex += data.length; + + if (autoUpdateLog) { + // Call getLog() again after a certain interval (e.g., 1 second) + setTimeout(getLog, 1000); + } + }); + } + } + + // Retrieve the saved header visibility preference from localStorage + var headerVisibility = localStorage.getItem('headerVisibility'); + + // Set the initial header visibility based on the saved preference or default to 'visible' + if (headerVisibility === 'hidden') { + $('#toggle-header').prop('checked', false); + $('#header').hide(); + } else { + $('#toggle-header').prop('checked', true); + $('#header').show(); + } + + // Handle the toggle header change event + $('#toggle-header').change(function () { + if (this.checked) { + $('#header').show(); + // Save the header visibility preference as 'visible' in localStorage + localStorage.setItem('headerVisibility', 'visible'); + } else { + $('#header').hide(); + // Save the header visibility preference as 'hidden' in localStorage + localStorage.setItem('headerVisibility', 'hidden'); + } + }); + + chart.render(); + + if (!localStorage.getItem("annotations")) localStorage.setItem("annotations", true); + if (!localStorage.getItem("dark-mode")) localStorage.setItem("dark-mode", true); + if (!localStorage.getItem("sort-by")) localStorage.setItem("sort-by", "Name ascending"); + + // Restore settings from localStorage on page load + $('#annotations').prop("checked", localStorage.getItem("annotations") === "true"); + $('#dark-mode').prop("checked", localStorage.getItem("dark-mode") === "true"); + + // Handle the annotation toggle click event + $('#annotations').click(() => { + var isChecked = $('#annotations').prop("checked"); + localStorage.setItem("annotations", isChecked); + updateAnnotations(); + }); + + // Handle the dark mode toggle click event + $('#dark-mode').click(() => { + var isChecked = $('#dark-mode').prop("checked"); + localStorage.setItem("dark-mode", isChecked); + toggleDarkMode(); + }); + + $('#startDate').val(formatDate(startDate)); + $('#endDate').val(formatDate(endDate)); + + sortBy = localStorage.getItem("sort-by"); + if (sortBy.includes("Points")) sortField = 'points'; + else if (sortBy.includes("Last activity")) sortField = 'last_activity'; + else sortField = 'name'; + $('#sorting-by').text(sortBy); + getStreamers(); + + updateAnnotations(); + toggleDarkMode(); + + // Retrieve log checkbox state from localStorage and update UI accordingly + var logCheckboxState = localStorage.getItem('logCheckboxState'); + $('#log').prop('checked', logCheckboxState === 'true'); + if (logCheckboxState === 'true') { + isLogCheckboxChecked = true; + $('#auto-update-log').show(); + $('#log-box').show(); + // Start continuously updating the log content + getLog(); + } + + // Handle the log checkbox change event + $('#log').change(function () { + isLogCheckboxChecked = $(this).prop('checked'); + localStorage.setItem('logCheckboxState', isLogCheckboxChecked); + + if (isLogCheckboxChecked) { + $('#log-box').show(); + $('#auto-update-log').show(); + getLog(); + $('html, body').scrollTop($(document).height()); + } else { + $('#log-box').hide(); + $('#auto-update-log').hide(); + // Clear log content when checkbox is unchecked + // $("#log-content").text(''); + } + }); +}); + +function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) month = '0' + month; + if (day.length < 2) day = '0' + day; + + return [year, month, day].join('-'); +} + +function changeStreamer(streamer, index) { + $("li").removeClass("is-active") + $("li").eq(index - 1).addClass('is-active'); + currentStreamer = streamer; + + // Update the chart title with the current streamer's name + options.title.text = `${streamer.replace(".json", "")}'s channel points (dates are displayed in UTC)`; + chart.updateOptions(options); + + // Save the selected streamer in localStorage + localStorage.setItem("selectedStreamer", currentStreamer); + + getStreamerData(streamer); +} + +function getStreamerData(streamer) { + if (currentStreamer == streamer) { + $.getJSON(`./json/${streamer}`, { + startDate: formatDate(startDate), + endDate: formatDate(endDate) + }, function (response) { + chart.updateSeries([{ + name: streamer.replace(".json", ""), + data: response["series"] + }], true) + clearAnnotations(); + annotations = response["annotations"]; + updateAnnotations(); + setTimeout(function () { + getStreamerData(streamer); + }, 300000); // 5 minutes + }); + } +} + +function getAllStreamersData() { + $.getJSON(`./json_all`, function (response) { + for (var i in response) { + chart.appendSeries({ + name: response[i]["name"].replace(".json", ""), + data: response[i]["data"]["series"] + }, true) + } + }); +} + +function getStreamers() { + $.getJSON('streamers', function (response) { + streamersList = response; + sortStreamers(); + + // Restore the selected streamer from localStorage on page load + var selectedStreamer = localStorage.getItem("selectedStreamer"); + + if (selectedStreamer) { + currentStreamer = selectedStreamer; + } else { + // If no selected streamer is found, default to the first streamer in the list + currentStreamer = streamersList.length > 0 ? streamersList[0].name : null; + } + + // Ensure the selected streamer is still active and scrolled into view + renderStreamers(); + }); +} + +function renderStreamers() { + $("#streamers-list").empty(); + var promised = new Promise((resolve, reject) => { + streamersList.forEach((streamer, index, array) => { + displayname = streamer.name.replace(".json", ""); + if (sortField == 'points') displayname = "" + streamer['points'] + " " + displayname; + else if (sortField == 'last_activity') displayname = "" + formatDate(streamer['last_activity']) + " " + displayname; + var isActive = currentStreamer === streamer.name; + if (!isActive && localStorage.getItem("selectedStreamer") === null && index === 0) { + isActive = true; + currentStreamer = streamer.name; + } + var activeClass = isActive ? 'is-active' : ''; + var listItem = `
  • ${displayname}
  • `; + $("#streamers-list").append(listItem); + if (isActive) { + // Scroll the selected streamer into view + document.getElementById(`streamer-${streamer.name}`).scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + if (index === array.length - 1) resolve(); + }); + }); + promised.then(() => { + changeStreamer(currentStreamer, streamersList.findIndex(streamer => streamer.name === currentStreamer) + 1); + }); +} + +function sortStreamers() { + streamersList = streamersList.sort((a, b) => { + return (a[sortField] > b[sortField] ? 1 : -1) * (sortBy.includes("ascending") ? 1 : -1); + }); +} + +function changeSortBy(option) { + sortBy = option.innerText.trim(); + if (sortBy.includes("Points")) sortField = 'points' + else if (sortBy.includes("Last activity")) sortField = 'last_activity' + else sortField = 'name'; + sortStreamers(); + renderStreamers(); + $('#sorting-by').text(sortBy); + localStorage.setItem("sort-by", sortBy); +} + +function updateAnnotations() { + if ($('#annotations').prop("checked") === true) { + clearAnnotations() + if (annotations && annotations.length > 0) + annotations.forEach((annotation, index) => { + annotations[index]['id'] = `id-${index}` + chart.addXaxisAnnotation(annotation, true) + }) + } else clearAnnotations() +} + +function clearAnnotations() { + if (annotations && annotations.length > 0) + annotations.forEach((annotation, index) => { + chart.removeAnnotation(annotation['id']) + }) + chart.clearAnnotations(); +} + +// Toggle +$('#annotations').click(() => { + updateAnnotations(); +}); +$('#dark-mode').click(() => { + toggleDarkMode(); +}); + +$('.dropdown').click(() => { + $('.dropdown').toggleClass('is-active'); +}); + +// Input date +$('#startDate').change(() => { + startDate = new Date($('#startDate').val()); + getStreamerData(currentStreamer); +}); +$('#endDate').change(() => { + endDate = new Date($('#endDate').val()); + getStreamerData(currentStreamer); +}); diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..85b1a2a --- /dev/null +++ b/assets/style.css @@ -0,0 +1,70 @@ +html, +body { + overflow-x: hidden; + overflow-y: auto; +} + +a { + text-decoration: none; +} + +.tabs ul { + -webkit-flex-direction: column; + flex-direction: column; + flex-grow: 0px; +} + +.tabs { + max-height: 500px; + overflow-y: auto; + direction: rtl; +} + +.checkbox-label { + font-size: 20px; + padding-left: 15px; + font-weight: bold +} + +::-webkit-scrollbar { + width: 20px; +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: #d6dee1; +} + +::-webkit-scrollbar-thumb { + background-color: #d6dee1; + border-radius: 20px; +} + +::-webkit-scrollbar-thumb { + background-color: #d6dee1; + border-radius: 20px; + border: 6px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #a8bbbf; +} + +#log-content { + text-align: left; + white-space: pre-wrap; + max-height: 400px; + padding: 0; +} + +#auto-update-log { + display: none; + background-color: transparent; + font-size: 20px; + padding: 0; + border-radius: 5px; +} \ No newline at end of file diff --git a/example.py b/example.py new file mode 100644 index 0000000..f3b0579 --- /dev/null +++ b/example.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +import logging +from colorama import Fore +from TwitchChannelPointsMiner import TwitchChannelPointsMiner +from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette +from TwitchChannelPointsMiner.classes.Chat import ChatPresence +from TwitchChannelPointsMiner.classes.Discord import Discord +from TwitchChannelPointsMiner.classes.Webhook import Webhook +from TwitchChannelPointsMiner.classes.Telegram import Telegram +from TwitchChannelPointsMiner.classes.Matrix import Matrix +from TwitchChannelPointsMiner.classes.Pushover import Pushover +from TwitchChannelPointsMiner.classes.Settings import Priority, Events, FollowersOrder +from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings + +twitch_miner = TwitchChannelPointsMiner( + username="your-twitch-username", + password="write-your-secure-psw", # If no password will be provided, the script will ask interactively + claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on the startup + priority=[ # Custom priority in this case for example: + Priority.STREAK, # - We want first of all to catch all watch streak from all streamers + Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers + Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) + ], + enable_analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + disable_ssl_cert_verification=False, # Set to True at your own risk and only to fix SSL: CERTIFICATE_VERIFY_FAILED error + disable_at_in_nickname=False, # Set to True if you want to check for your nickname mentions in the chat even without @ sign + logger_settings=LoggerSettings( + save=True, # If you want to save logs in a file (suggested) + console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info + console_username=False, # Adds a username to every console log line if True. Also adds it to Telegram, Discord, etc. Useful when you have several accounts + auto_clear=True, # Create a file rotation handler with interval = 1D and backupCount = 7 if True (default) + time_zone="", # Set a specific time zone for console and file loggers. Use tz database names. Example: "America/Denver" + file_level=logging.DEBUG, # Level of logs - If you think the log file it's too big, use logging.INFO + emoji=True, # On Windows, we have a problem printing emoji. Set to false if you have a problem + less=False, # If you think that the logs are too verbose, set this to True + colored=True, # If you want to print colored text + color_palette=ColorPalette( # You can also create a custom palette color (for the common message). + STREAMER_online="GREEN", # Don't worry about lower/upper case. The script will parse all the values. + streamer_offline="red", # Read more in README.md + BET_wiN=Fore.MAGENTA # Color allowed are: [BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET]. + ), + telegram=Telegram( # You can omit or set to None if you don't want to receive updates on Telegram + chat_id=123456789, # Chat ID to send messages @getmyid_bot + token="123456789:shfuihreuifheuifhiu34578347", # Telegram API token @BotFather + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the chat + disable_notification=True, # Revoke the notification (sound/vibration) + ), + discord=Discord( + webhook_api="https://discord.com/api/webhooks/0123456789/0a1B2c3D4e5F6g7H8i9J", # Discord Webhook URL + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the chat + ), + webhook=Webhook( + endpoint="https://example.com/webhook", # Webhook URL + method="GET", # GET or POST + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, + Events.BET_LOSE, Events.CHAT_MENTION], # Only these events will be sent to the endpoint + ), + matrix=Matrix( + username="twitch_miner", # Matrix username (without homeserver) + password="...", # Matrix password + homeserver="matrix.org", # Matrix homeserver + room_id="...", # Room ID + events=[Events.STREAMER_ONLINE, Events.STREAMER_OFFLINE, Events.BET_LOSE], # Only these events will be sent + ), + pushover=Pushover( + userkey="YOUR-ACCOUNT-TOKEN", # Login to https://pushover.net/, the user token is on the main page + token="YOUR-APPLICATION-TOKEN", # Create a application on the website, and use the token shown in your application + priority=0, # Read more about priority here: https://pushover.net/api#priority + sound="pushover", # A list of sounds can be found here: https://pushover.net/api#sounds + events=[Events.CHAT_MENTION, Events.DROP_CLAIM], # Only these events will be sent + ) + ), + streamer_settings=StreamerSettings( + make_predictions=True, # If you want to Bet / Make prediction + follow_raid=True, # Follow raid to obtain more points + claim_drops=True, # We can't filter rewards base on stream. Set to False for skip viewing counter increase and you will never obtain a drop reward from this script. Issue #21 + claim_moments=True, # If set to True, https://help.twitch.tv/s/article/moments will be claimed when available + watch_streak=True, # If a streamer go online change the priority of streamers array and catch the watch screak. Issue #11 + chat=ChatPresence.ONLINE, # Join irc chat to increase watch-time [ALWAYS, NEVER, ONLINE, OFFLINE] + bet=BetSettings( + strategy=Strategy.SMART, # Choose you strategy! + percentage=5, # Place the x% of your channel points + percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART strategy) + max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value + stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33 + delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer + delay=6, + minimum_points=20000, # Place the bet only if we have at least 20k points. Issue #113 + filter_condition=FilterCondition( + by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value + value=800 + ) + ) + ) +) + +# You can customize the settings for each streamer. If not settings were provided, the script would use the streamer_settings from TwitchChannelPointsMiner. +# If no streamer_settings are provided in TwitchChannelPointsMiner the script will use default settings. +# The streamers array can be a String -> username or Streamer instance. + +# The settings priority are: settings in mine function, settings in TwitchChannelPointsMiner instance, default settings. +# For example, if in the mine function you don't provide any value for 'make_prediction' but you have set it on TwitchChannelPointsMiner instance, the script will take the value from here. +# If you haven't set any value even in the instance the default one will be used + +#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server + +twitch_miner.mine( + [ + Streamer("streamer-username01", settings=StreamerSettings(make_predictions=True , follow_raid=False , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=True, percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.LTE, value=800 ) ) )), + Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , stealth_mode=False, percentage_gap=20 , max_points=1234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_POINTS, where=Condition.GTE, value=250 ) ) )), + Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=False, percentage_gap=30 , max_points=50000 , filter_condition=FilterCondition(by=OutcomeKeys.ODDS, where=Condition.LT, value=300 ) ) )), + Streamer("streamer-username04", settings=StreamerSettings(make_predictions=False , follow_raid=True , watch_streak=True )), + Streamer("streamer-username05", settings=StreamerSettings(make_predictions=True , follow_raid=True , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.HIGH_ODDS , percentage=7 , stealth_mode=True, percentage_gap=20 , max_points=90 , filter_condition=FilterCondition(by=OutcomeKeys.PERCENTAGE_USERS, where=Condition.GTE, value=300 ) ) )), + Streamer("streamer-username06"), + Streamer("streamer-username07"), + Streamer("streamer-username08"), + "streamer-username09", + "streamer-username10", + "streamer-username11" + ], # Array of streamers (order = priority) + followers=False, # Automatic download the list of your followers + followers_order=FollowersOrder.ASC # Sort the followers list by follow date. ASC or DESC +) diff --git a/pickle_view.py b/pickle_view.py new file mode 100644 index 0000000..b77a577 --- /dev/null +++ b/pickle_view.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +# Simple script to view contents of a cookie file stored in a pickle format + +import pickle +import sys + +if __name__ == '__main__': + argv = sys.argv + if len(argv) <= 1: + print("Specify a pickle file as a parameter, e.g. cookies/user.pkl") + else: + print(pickle.load(open(argv[1], 'rb'))) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..51a1e42 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +requests +websocket-client +pillow +python-dateutil +emoji +millify +pre-commit +colorama +flask +irc +pandas +pytz diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3feebfa --- /dev/null +++ b/setup.py @@ -0,0 +1,58 @@ +from os import path + +import setuptools +import re + + +def read(fname): + return open(path.join(path.dirname(__file__), fname), encoding="utf-8").read() + + +metadata = dict( + re.findall( + r"""__([a-z]+)__ = "([^"]+)""", read("TwitchChannelPointsMiner/__init__.py") + ) +) + +setuptools.setup( + name="Twitch-Channel-Points-Miner-v2", + version=metadata["version"], + author="Tkd-Alex (Alessandro Maggio) and rdavydov (Roman Davydov)", + author_email="alex.tkd.alex@gmail.com", + description="A simple script that will watch a stream for you and earn the channel points.", + license="GPLv3+", + keywords="python bot streaming script miner twtich channel-points", + url="https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2", + packages=setuptools.find_packages(), + include_package_data=True, + install_requires=[ + "requests", + "websocket-client", + "pillow", + "python-dateutil", + "emoji", + "millify", + "pre-commit", + "colorama", + "flask", + "irc", + "pandas", + "pytz" + ], + long_description=read("README.md"), + long_description_content_type="text/markdown", + classifiers=[ + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Operating System :: OS Independent", + "Topic :: Utilities", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Version Control :: Git", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + ], + python_requires=">=3.6", +)

    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