diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 93f5c24..282126a 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -43,7 +43,7 @@ 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("seleniumwire").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logger = logging.getLogger(__name__) @@ -84,7 +84,8 @@ class TwitchChannelPointsMiner: ): # 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( + "Please edit your runner file (usually run.py) and try again.") logger.error("No username, exiting...") sys.exit(0) @@ -92,7 +93,8 @@ class TwitchChannelPointsMiner: Settings.enable_analytics = enable_analytics if enable_analytics is True: - Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) + Settings.analytics_path = os.path.join( + Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) self.username = username @@ -105,7 +107,8 @@ class TwitchChannelPointsMiner: streamer_settings.bet.default() Settings.streamer_settings = streamer_settings - user_agent = get_user_agent("FIREFOX") + # 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 @@ -129,15 +132,18 @@ class TwitchChannelPointsMiner: # 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") + 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 the version {current_version} of this script") + logger.info( + f"You are running the 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]: @@ -153,7 +159,7 @@ class TwitchChannelPointsMiner: # 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 ) @@ -161,7 +167,8 @@ class TwitchChannelPointsMiner: http_server.name = "Analytics Thread" http_server.start() else: - logger.error("Can't start analytics(), please set enable_analytics=True") + logger.error( + "Can't start analytics(), please set enable_analytics=True") def mine( self, @@ -207,7 +214,8 @@ class TwitchChannelPointsMiner: streamers_dict[username] = streamer if followers is True: - followers_array = self.twitch.get_followers(order=followers_order) + followers_array = self.twitch.get_followers( + order=followers_order) logger.info( f"Load {len(followers_array)} followers from your profile!", extra={"emoji": ":clipboard:"}, @@ -230,7 +238,8 @@ class TwitchChannelPointsMiner: if isinstance(streamers_dict[username], Streamer) is True else Streamer(username) ) - streamer.channel_id = self.twitch.get_channel_id(username) + streamer.channel_id = self.twitch.get_channel_id( + username) streamer.settings = set_default_settings( streamer.settings, Settings.streamer_settings ) @@ -272,7 +281,8 @@ class TwitchChannelPointsMiner: # 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) + at_least_one_value_in_settings_is( + self.streamers, "claim_drops", True) is True ): self.sync_campaigns_thread = threading.Thread( @@ -298,8 +308,8 @@ class TwitchChannelPointsMiner: # 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}") - + # print(f"!!!!!!!!!!!!!! USER_ID: {user_id}") + # Fixes 'ERR_BADAUTH' if not user_id: logger.error("No user_id, exiting...") @@ -331,7 +341,8 @@ class TwitchChannelPointsMiner: if streamer.settings.make_predictions is True: self.ws_pool.submit( - PubsubTopic("predictions-channel-v1", streamer=streamer) + PubsubTopic("predictions-channel-v1", + streamer=streamer) ) refresh_context = time.time() @@ -348,7 +359,8 @@ class TwitchChannelPointsMiner: 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]) + WebSocketsPool.handle_reconnection( + self.ws_pool.ws[index]) if ((time.time() - refresh_context) // 60) >= 30: refresh_context = time.time() @@ -445,4 +457,4 @@ class TwitchChannelPointsMiner: logger.info( f"{self.streamers[streamer_index].print_history()}", extra={"emoji": ":moneybag:"}, - ) \ No newline at end of file + ) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 1c7a048..11d7d3c 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.4" +__version__ = "1.6.0" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 9424776..9f7025c 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -16,10 +16,17 @@ from pathlib import Path from secrets import choice, token_hex import json +import pickle +from base64 import urlsafe_b64decode + +from hashlib import sha256 from base64 import urlsafe_b64decode import requests +from undetected_chromedriver import ChromeOptions +import seleniumwire.undetected_chromedriver.v2 as uc + from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign from TwitchChannelPointsMiner.classes.entities.Drop import Drop from TwitchChannelPointsMiner.classes.Exceptions import ( @@ -47,6 +54,71 @@ from TwitchChannelPointsMiner.utils import ( logger = logging.getLogger(__name__) +HEX_CHARS = '0123456789abcdef' +difficulty_1 = 10 +subchallengeCount = 2 +platformInputs = "tp-v2-input" + + +def get_time_now() -> int: + time_now = f'{time.time()}' + return int(time_now.replace('.', '')[:13]) + + +def random_string(k=32): + return ''.join(random.choices(HEX_CHARS, k=k)) + + +def string_to_sha256(string_to: str): + return sha256(string_to.encode()).hexdigest() + + +def get_hash_difficulty(hash_string): + return 4503599627370496 / (int(f'0x{hash_string[:13]}', 16) + 1) + + +def prof_work(config, _id, work_time): + f_list = [] + difficulty = config['difficulty'] / config['subchallengeCount'] + start_string = string_to_sha256( + platformInputs + ',\x20' + str(work_time) + ',\x20' + _id) + for _ in range(config['subchallengeCount']): + d = 1 + while True: + start_string2 = string_to_sha256(str(d) + ',\x20' + start_string) + if (get_hash_difficulty(start_string2) >= difficulty): + f_list.append(d) + start_string = start_string2 + break + d += 1 + return { + 'answers': f_list, + 'finalHash': start_string + } + + +def get_kpsdk_cd(): + config = { + "platformInputs": "tp-v2-input", + "difficulty": 10, + "subchallengeCount": 2 + } + t0 = time.perf_counter() + _id = random_string() + work_time = get_time_now() - 527 + prof_of_work = prof_work(config, _id, work_time) + t1 = time.perf_counter_ns() + + duration = round(1000 * (t1 - t0)) / 1000 + return { + 'answers': prof_of_work['answers'], + 'rst': time.perf_counter_ns(), + 'st': time.perf_counter_ns(), + 'd': duration, + 'id': _id, + 'workTime': work_time, + } + class Twitch(object): __slots__ = [ @@ -127,22 +199,26 @@ class Twitch(object): def get_spade_url(self, streamer): try: # fixes AttributeError: 'NoneType' object has no attribute 'group' - #headers = {"User-Agent": self.user_agent} + # headers = {"User-Agent": self.user_agent} from TwitchChannelPointsMiner.constants import USER_AGENTS - headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + # headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + headers = {"User-Agent": USER_AGENTS["Windows"]["CHROME"]} - main_page_request = requests.get(streamer.streamer_url, headers=headers) + main_page_request = requests.get( + streamer.streamer_url, headers=headers) response = main_page_request.text - #logger.info(response) + # 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) + 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}") + logger.error( + f"Something went wrong during extraction of 'spade_url': {e}") def get_broadcast_id(self, streamer): json_data = copy.deepcopy(GQLOperations.WithIsStreamLiveQuery) @@ -156,7 +232,8 @@ class Twitch(object): raise StreamerIsOfflineException def get_stream_info(self, streamer): - json_data = copy.deepcopy(GQLOperations.VideoPlayerStreamInfoOverlayChannel) + json_data = copy.deepcopy( + GQLOperations.VideoPlayerStreamInfoOverlayChannel) json_data["variables"] = {"channel": streamer.username} response = self.post_gql_request(json_data) if response != {}: @@ -228,7 +305,8 @@ class Twitch(object): logger.info( f"Joining raid from {streamer} to {raid.target_login}!", - extra={"emoji": ":performing_arts:", "event": Events.JOIN_RAID}, + extra={"emoji": ":performing_arts:", + "event": Events.JOIN_RAID}, ) def viewer_is_mod(self, streamer): @@ -294,45 +372,109 @@ class Twitch(object): ): return self.integrity try: - response = requests.post( + # is_bad_bot becomes true when I use headless mode. + 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) + + driver = uc.Chrome( + options=options, use_subprocess=True # , executable_path=EXECUTABLE_PATH + ) + driver.minimize_window() + driver.get('https://www.twitch.tv/robots.txt') + cookies = pickle.load(open(self.cookies_file, "rb")) + for cookie in cookies: + driver.add_cookie(cookie) + driver.get('https://www.twitch.tv/settings/profile') + + # Print request headers + # for request in driver.requests: + # logger.info(request.url) # <--------------- Request url + # logger.info(request.headers) # <----------- Request headers + # logger.info(request.response.headers) # <-- Response headers + + # Set correct user agent + selenium_user_agent = driver.execute_script( + "return navigator.userAgent;") + + self.user_agent = selenium_user_agent + + session = requests.Session() + kpsdk_cd = json.dumps(get_kpsdk_cd()) + + for cookie in driver.get_cookies(): + session.cookies.set(cookie['name'], cookie['value'], + domain=cookie['domain']) + if cookie["name"] == "KP_UIDz": + if cookie["value"] is not None: + kpsdk_ct = cookie["value"] + else: + logger.error("Can't extract kpsdk_ct") + + headers = { + 'Accept': '*/*', + 'Accept-Language': 'en-US,en;q=0.9,pt;q=0.8', + "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(), + 'Origin': 'https://www.twitch.tv', + 'Referer': 'https://www.twitch.tv/', + "User-Agent": self.user_agent, + # "User-Agent": selenium_user_agent, + "X-Device-Id": self.device_id, + 'x-kpsdk-cd': kpsdk_cd, + 'x-kpsdk-ct': kpsdk_ct + } + + response = session.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, - }, + headers=headers ) - 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}") - + + driver.close() + driver.quit() + + integrity_json = response.json() + + self.integrity = integrity_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}") + logger.error( + "Uh-oh, Twitch has detected this miner as a \"Bad Bot\"") + + self.integrity_expire = integrity_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): + def isBadBot(self, integrity): stripped_token: str = self.integrity.split('.')[2] + "==" - messy_json: str = urlsafe_b64decode(stripped_token.encode()).decode(errors="ignore") + 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") + # 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}") + # logger.info(f"decoded_header: {decoded_header}") if decoded_header.get("is_bad_bot", "false") != "false": return True else: @@ -383,11 +525,13 @@ class Twitch(object): streamers_watching += streamers_index[:2] elif ( - prior in [Priority.POINTS_ASCENDING, Priority.POINTS_DESCEDING] + prior in [Priority.POINTS_ASCENDING, + Priority.POINTS_DESCEDING] and len(streamers_watching) < 2 ): items = [ - {"points": streamers[index].channel_points, "index": index} + {"points": streamers[index].channel_points, + "index": index} for index in streamers_index ] items = sorted( @@ -397,7 +541,8 @@ class Twitch(object): True if prior == Priority.POINTS_DESCEDING else False ), ) - streamers_watching += [item["index"] for item in items][:2] + streamers_watching += [item["index"] + for item in items][:2] elif prior == Priority.STREAK and len(streamers_watching) < 2: """ @@ -413,7 +558,8 @@ class Twitch(object): and ( streamers[index].offline_at == 0 or ( - (time.time() - streamers[index].offline_at) + (time.time() - + streamers[index].offline_at) // 60 ) > 30 @@ -439,7 +585,8 @@ class Twitch(object): ] streamers_with_multiplier = sorted( streamers_with_multiplier, - key=lambda x: streamers[x].total_points_multiplier(), + key=lambda x: streamers[x].total_points_multiplier( + ), reverse=True, ) streamers_watching += streamers_with_multiplier[:2] @@ -508,10 +655,12 @@ class Twitch(object): ) except requests.exceptions.ConnectionError as e: - logger.error(f"Error while trying to send minute watched: {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}") + logger.error( + f"Error while trying to send minute watched: {e}") self.__chuncked_sleep( next_iteration - time.time(), chunk_size=chunk_size @@ -520,7 +669,8 @@ class Twitch(object): if streamers_watching == []: self.__chuncked_sleep(60, chunk_size=chunk_size) except Exception: - logger.error("Exception raised in send minute watched", exc_info=True) + 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 @@ -538,11 +688,12 @@ class Twitch(object): streamer.activeMultipliers = community_points["activeMultipliers"] if community_points["availableClaim"] is not None: - self.claim_bonus(streamer, community_points["availableClaim"]["id"]) + 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 + # selector_index = 0 if decision["choice"] == "A" else 1 logger.info( f"Going to complete bet for {event}", @@ -571,7 +722,7 @@ class Twitch(object): 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(selector_index)}", f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(decision['choice'])}", extra={ "emoji": ":four_leaf_clover:", @@ -635,7 +786,8 @@ class Twitch(object): # === CAMPAIGNS / DROPS / INVENTORY === # def __get_campaign_ids_from_streamer(self, streamer): - json_data = copy.deepcopy(GQLOperations.DropsHighlightService_AvailableDrops) + json_data = copy.deepcopy( + GQLOperations.DropsHighlightService_AvailableDrops) json_data["variables"] = {"channelID": streamer.channel_id} response = self.post_gql_request(json_data) try: @@ -663,7 +815,8 @@ class Twitch(object): response = self.post_gql_request(GQLOperations.ViewerDropsDashboard) campaigns = response["data"]["currentUser"]["dropCampaigns"] if status is not None: - campaigns = list(filter(lambda x: x["status"] == status.upper(), campaigns)) + campaigns = list( + filter(lambda x: x["status"] == status.upper(), campaigns)) return campaigns def __get_campaigns_details(self, campaigns): @@ -672,7 +825,8 @@ class Twitch(object): for chunk in chunks: json_data = [] for campaign in chunk: - json_data.append(copy.deepcopy(GQLOperations.DropCampaignDetails)) + json_data.append(copy.deepcopy( + GQLOperations.DropCampaignDetails)) json_data[-1]["variables"] = { "dropID": campaign["id"], "channelLogin": f"{self.twitch_login.get_user_id()}", @@ -703,7 +857,8 @@ class Twitch(object): campaigns[i].sync_drops( progress["timeBasedDrops"], self.claim_drop ) - campaigns[i].clear_drops() # Remove all the claimed drops + # Remove all the claimed drops + campaigns[i].clear_drops() break return campaigns @@ -713,7 +868,8 @@ class Twitch(object): ) json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) - json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} + 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"] != [] diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 4da3a02..0adfbac 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -3,16 +3,15 @@ URL = "https://www.twitch.tv" IRC = "irc.chat.twitch.tv" IRC_PORT = 6667 WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" -#CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" # Browser -CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App -#CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App +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" USER_AGENTS = { "Windows": { - #"CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", - 'CHROME': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", + '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": { @@ -20,7 +19,7 @@ USER_AGENTS = { "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-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" } } diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index a9d71eb..6a4bd9e 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -32,7 +32,8 @@ def float_round(number, ndigits=2): def server_time(message_data): return ( - datetime.fromtimestamp(message_data["server_time"], timezone.utc).isoformat() + 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" @@ -54,12 +55,15 @@ def create_nonce(length=30) -> str: return nonce # for mobile-token + + def get_user_agent(browser: str) -> str: - """try: + try: return USER_AGENTS[platform.system()][browser] except KeyError: - return USER_AGENTS["Linux"]["FIREFOX"]""" - return USER_AGENTS["Android"]["App"] + # return USER_AGENTS["Linux"]["FIREFOX"] + return USER_AGENTS["Windows"]["CHROME"] + # return USER_AGENTS["Android"]["App"] def remove_emoji(string: str) -> str: @@ -141,6 +145,7 @@ def set_default_settings(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) @@ -155,7 +160,7 @@ def percentage(a, b): def create_chunks(lst, n): - return [lst[i : (i + n)] for i in range(0, len(lst), n)] # noqa: E203 + return [lst[i: (i + n)] for i in range(0, len(lst), n)] # noqa: E203 def download_file(name, fpath): diff --git a/requirements.txt b/requirements.txt index 23e6dbb..9e67bfe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,6 @@ flask irc pandas browser_cookie3 +selenium +selenium-wire +undetected_chromedriver diff --git a/setup.py b/setup.py index 1886864..8626e5d 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,10 @@ setuptools.setup( "flask", "irc", "pandas", - "browser_cookie3" + "browser_cookie3", + "selenium", + "selenium-wire", + "undetected_chromedriver" ], long_description=read("README.md"), long_description_content_type="text/markdown",