diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml
index 38bcdbe..8eba255 100644
--- a/.github/workflows/deploy-docker.yml
+++ b/.github/workflows/deploy-docker.yml
@@ -22,7 +22,7 @@ jobs:
uses: docker/setup-buildx-action@v3.3.0
- name: Login to DockerHub
- uses: docker/login-action@v3.1.0
+ uses: docker/login-action@v3.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
@@ -38,7 +38,7 @@ jobs:
- name: Build and push AMD64, ARM64, ARMv7
id: docker_build
- uses: docker/build-push-action@v5.3.0
+ uses: docker/build-push-action@v5.4.0
with:
context: .
push: true
diff --git a/README.md b/README.md
index dc4ae5e..7223cfb 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,10 @@ If you want to offer me a coffee, I would be grateful! ❤️
| | |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
+|
|`bc1qq49mvgda2zw4f9kta0a85xztwuxewqwac5eckd` _(BTC)_|
|
|`DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` _(DOGE)_|
+|
|https://www.donationalerts.com/r/rdavydov|
+|
|https://boosty.to/rdavydov/donate|
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.
diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py
index 8162568..f7e415c 100644
--- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py
+++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py
@@ -190,6 +190,7 @@ class TwitchChannelPointsMiner:
if Settings.enable_analytics is True:
from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer
+ days_ago = days_ago if days_ago <= 365*15 else 365*15
http_server = AnalyticsServer(
host=host, port=port, refresh=refresh, days_ago=days_ago, username=self.username
)
diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py
index b6df4f8..eb5f952 100644
--- a/TwitchChannelPointsMiner/__init__.py
+++ b/TwitchChannelPointsMiner/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-__version__ = "1.9.5"
+__version__ = "1.9.7"
from .TwitchChannelPointsMiner import TwitchChannelPointsMiner
__all__ = [
diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py
index f9661f9..ea12537 100644
--- a/TwitchChannelPointsMiner/classes/Twitch.py
+++ b/TwitchChannelPointsMiner/classes/Twitch.py
@@ -11,14 +11,14 @@ import random
import re
import string
import time
-# from datetime import datetime
+# import json
+import requests
+import validators
from pathlib import Path
from secrets import choice, token_hex
-
-# import json
+# from urllib.parse import quote
# from base64 import urlsafe_b64decode
-
-import requests
+# from datetime import datetime
from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign
from TwitchChannelPointsMiner.classes.entities.Drop import Drop
@@ -79,7 +79,7 @@ class Twitch(object):
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})\";"
+ r'window\.__twilightBuildID\s*=\s*"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"'
)
def login(self):
@@ -433,7 +433,8 @@ class Twitch(object):
)
> 30
)
- and streamers[index].stream.minute_watched < 7 # fix #425
+ # fix #425
+ and streamers[index].stream.minute_watched < 7
):
streamers_watching.append(index)
if len(streamers_watching) == 2:
@@ -467,14 +468,87 @@ class Twitch(object):
streamers_watching = streamers_watching[:2]
for index in streamers_watching:
- next_iteration = time.time() + 60 / len(streamers_watching)
+ # next_iteration = time.time() + 60 / len(streamers_watching)
+ next_iteration = time.time() + 20 / len(streamers_watching)
try:
+ ####################################
+ # Start of fix for 2024/5 API Change
+ # Create the JSON data for the GraphQL request
+ json_data = copy.deepcopy(
+ GQLOperations.PlaybackAccessToken)
+ json_data["variables"] = {
+ "login": streamers[index].username,
+ "isLive": True,
+ "isVod": False,
+ "vodID": "",
+ # "playerType": "site"
+ "playerType": "picture-by-picture"
+ }
+
+ # Get signature and value using the post_gql_request method
+ responsePlaybackAccessToken = self.post_gql_request(
+ json_data)
+ logger.debug(
+ f"Sent PlaybackAccessToken request for {streamers[index]}"
+ )
+ signature = responsePlaybackAccessToken["data"]['streamPlaybackAccessToken']["signature"]
+ value = responsePlaybackAccessToken["data"]['streamPlaybackAccessToken']["value"]
+ if not signature or not value:
+ continue
+
+ # encoded_value = quote(json.dumps(value))
+
+ # Construct the URL for the broadcast qualities
+ RequestBroadcastQualitiesURL = f"https://usher.ttvnw.net/api/channel/hls/{streamers[index].username}.m3u8?sig={signature}&token={value}"
+
+ # Get list of video qualities
+ responseBroadcastQualities = requests.get(RequestBroadcastQualitiesURL, headers={
+ "User-Agent": self.user_agent}, timeout=20) # timeout=60
+ logger.debug(
+ f"Send RequestBroadcastQualitiesURL request for {streamers[index]} - Status code: {responseBroadcastQualities.status_code}"
+ )
+ if responseBroadcastQualities.status_code != 200:
+ continue
+ BroadcastQualities = responseBroadcastQualities.text
+
+ # Just takes the last line, which should be the URL for the lowest quality
+ BroadcastLowestQualityURL = BroadcastQualities.split(
+ "\n")[-1]
+ if not validators.url(BroadcastLowestQualityURL):
+ continue
+
+ # Get list of video URLs
+ responseStreamURLList = requests.get(BroadcastLowestQualityURL, headers={
+ "User-Agent": self.user_agent}, timeout=20) # timeout=60
+ logger.debug(
+ f"Send BroadcastLowestQualityURL request for {streamers[index]} - Status code: {responseStreamURLList.status_code}"
+ )
+ if responseStreamURLList.status_code != 200:
+ continue
+ StreamURLList = responseStreamURLList.text
+
+ # Just takes the last line, which should be the URL for the lowest quality
+ StreamLowestQualityURL = StreamURLList.split("\n")[-2]
+ if not validators.url(StreamLowestQualityURL):
+ continue
+
+ # Perform a HEAD request to simulate watching the stream
+ responseStreamLowestQualityURL = requests.head(StreamLowestQualityURL, headers={
+ "User-Agent": self.user_agent}, timeout=20) # timeout=60
+ logger.debug(
+ f"Send StreamLowestQualityURL request for {streamers[index]} - Status code: {responseStreamLowestQualityURL.status_code}"
+ )
+ if responseStreamLowestQualityURL.status_code != 200:
+ continue
+ # End of fix for 2024/5 API Change
+ ##################################
response = requests.post(
streamers[index].stream.spade_url,
data=streamers[index].stream.encode_payload(),
headers={"User-Agent": self.user_agent},
- timeout=60,
+ # timeout=60,
+ timeout=20,
)
logger.debug(
f"Send minute watched request for {streamers[index]} - Status code: {response.status_code}"
@@ -543,7 +617,8 @@ class Twitch(object):
)
if streamers_watching == []:
- self.__chuncked_sleep(60, chunk_size=chunk_size)
+ # self.__chuncked_sleep(60, chunk_size=chunk_size)
+ self.__chuncked_sleep(20, chunk_size=chunk_size)
except Exception:
logger.error(
"Exception raised in send minute watched", exc_info=True)
diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py
index 93d7e14..c73ad4f 100644
--- a/TwitchChannelPointsMiner/constants.py
+++ b/TwitchChannelPointsMiner/constants.py
@@ -1,10 +1,13 @@
# Twitch endpoints
-URL = "https://www.twitch.tv"
+URL = "https://www.twitch.tv" # Browser, Apps
+# URL = "https://m.twitch.tv" # Mobile Browser
+# URL = "https://android.tv.twitch.tv" # 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 = "r8s4dac0uhzifbpu9sjdiwzctle17ff" # Mobile Browser
# CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App
# CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App
DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b"
@@ -46,6 +49,15 @@ class GQLOperations:
}
},
}
+ PlaybackAccessToken = {
+ "operationName": "PlaybackAccessToken",
+ "extensions": {
+ "persistedQuery": {
+ "version": 1,
+ "sha256Hash": "3093517e37e4f4cb48906155bcd894150aef92617939236d2508f3375ab732ce",
+ }
+ },
+ }
VideoPlayerStreamInfoOverlayChannel = {
"operationName": "VideoPlayerStreamInfoOverlayChannel",
"extensions": {
diff --git a/requirements.txt b/requirements.txt
index 51a1e42..eda41da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,3 +10,4 @@ flask
irc
pandas
pytz
+validators