From 94e81c99b4f1ec3201acc3dceb715c6ee2d2f8f1 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 15 Jun 2024 03:03:36 +0300 Subject: [PATCH] fix #522 --- TwitchChannelPointsMiner/classes/Twitch.py | 93 +++++++++++++++++++--- TwitchChannelPointsMiner/constants.py | 14 +++- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index f9661f9c..6d1cce9e 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -11,14 +11,14 @@ 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 @@ -433,7 +433,8 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): ) > 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 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): 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 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): ) 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 93d7e14c..c73ad4f2 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": {