From 1dee172a6cdfb9aaca7e392a912ebbc94cb8819c Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 1 Feb 2021 22:56:13 +0100 Subject: [PATCH 001/124] Call self.start_bet(event) inside place_bet(event) - Attempt to speed-up the bet system so we don't need to make a pause and skip others bet -- https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/28\#issuecomment-771075169 #28 --- .../classes/TwitchBrowser.py | 79 ++++++------------- .../classes/WebSocketsPool.py | 48 ++++------- TwitchChannelPointsMiner/utils.py | 4 - 3 files changed, 39 insertions(+), 92 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py index 0960142..1501330 100644 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ b/TwitchChannelPointsMiner/classes/TwitchBrowser.py @@ -7,7 +7,7 @@ from enum import Enum, auto from pathlib import Path from selenium import webdriver -from selenium.common.exceptions import JavascriptException, TimeoutException +from selenium.common.exceptions import JavascriptException from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions @@ -264,7 +264,6 @@ class TwitchBrowser: return False def start_bet(self, event: EventPrediction): - start_time = time.time() if bet_condition(self, event, logger) is True: for attempt in range(0, self.settings.max_attempts): logger.info( @@ -272,7 +271,7 @@ class TwitchBrowser: extra={"emoji": ":wrench:"}, ) self.browser.get(event.streamer.chat_url) - time.sleep(random.uniform(3, 5)) + time.sleep(random.uniform(1, 3)) self.__click_when_exist( Selectors.cookiePolicy, By.CSS_SELECTOR, @@ -284,12 +283,12 @@ class TwitchBrowser: self.__execute_script(Javascript.clearStyleChat, suppress_error=True) if self.__bet_chains_methods(event) is True: - return self.currently_is_betting, time.time() - start_time + return self.currently_is_betting logger.error( f"Attempt {attempt+1} failed!", extra={"emoji": ":wrench:"} ) self.__blank() # If we fail return to blank page - return False, time.time() - start_time + return False def __bet_chains_methods(self, event) -> bool: if self.__open_coins_menu(event) is True: @@ -299,67 +298,37 @@ class TwitchBrowser: return False def place_bet(self, event: EventPrediction): - logger.info( - f"Going to complete bet for {event} owned by {event.streamer}", - extra={"emoji": ":wrench:"}, - ) if event.status == "ACTIVE": + self.currently_is_betting = self.start_bet(event) if event.box_fillable and self.currently_is_betting: - - div_bet_is_open = False - self.__debug(event, "place_bet") - try: - WebDriverWait(self.browser, 1).until( - expected_conditions.visibility_of_element_located( - (By.XPATH, Selectors.betMainDivXP) - ) - ) - div_bet_is_open = True - except TimeoutException: + decision = event.bet.calculate(event.streamer.channel_points) + if decision["choice"] is not None: + selector_index = 1 if decision["choice"] == "A" else 2 logger.info( - "The bet div was not found, maybe It was closed. Attempting to open again... Hopefully in time!", + f"Decision: {event.bet.get_outcome(selector_index - 1)}", extra={"emoji": ":wrench:"}, ) - div_bet_is_open = self.__bet_chains_methods(event) - if div_bet_is_open is True: + + try: logger.info( - "Success! Bet div is now open, we can complete the bet!", + f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", extra={"emoji": ":wrench:"}, ) - - if div_bet_is_open is True: - decision = event.bet.calculate(event.streamer.channel_points) - if decision["choice"] is not None: - selector_index = 1 if decision["choice"] == "A" else 2 - logger.info( - f"Decision: {event.bet.get_outcome(selector_index - 1)}", - extra={"emoji": ":wrench:"}, - ) - - try: + if ( + self.__send_text_on_bet( + event, selector_index, decision["amount"] + ) + is True + ): logger.info( - f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", + f"Going to place the bet for {event}", extra={"emoji": ":wrench:"}, ) - if ( - self.__send_text_on_bet( - event, selector_index, decision["amount"] - ) - is True - ): - logger.info( - f"Going to place the bet for {event}", - extra={"emoji": ":wrench:"}, - ) - if self.__click_on_vote(event, selector_index) is True: - event.bet_placed = True - time.sleep(random.uniform(5, 10)) - except Exception: - logger.error("Exception raised", exc_info=True) - else: - logger.info( - "Sorry, unable to complete the bet. The bet div is still closed!" - ) + if self.__click_on_vote(event, selector_index) is True: + event.bet_placed = True + time.sleep(random.uniform(5, 10)) + except Exception: + logger.error("Exception raised", exc_info=True) else: logger.info( f"Sorry, unable to complete the bet. Event box fillable: {event.box_fillable}, the browser is betting: {self.currently_is_betting}" diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 1a46173..ae52595 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -12,12 +12,7 @@ from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants.twitch import WEBSOCKET -from TwitchChannelPointsMiner.utils import ( - _millify, - bet_condition, - calculate_start_after, - get_streamer_index, -) +from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_streamer_index logger = logging.getLogger(__name__) @@ -184,7 +179,7 @@ class WebSocketsPool: event_dict["prediction_window_seconds"] ) prediction_window_seconds -= ( - 25 if prediction_window_seconds <= 180 else 60 + 30 if prediction_window_seconds <= 120 else 60 ) event = EventPrediction( ws.streamers[streamer_index], @@ -205,34 +200,21 @@ class WebSocketsPool: ) is True ): - ws.events_predictions[event_id] = event - ( - start_bet_status, - execution_time, - ) = ws.browser.start_bet( - ws.events_predictions[event_id] + # place_bet_thread = threading.Timer(event.closing_bet_after(current_tmsp), ws.twitch.make_predictions, (ws.events_predictions[event_id],)) + start_after = event.closing_bet_after(current_tmsp) + + place_bet_thread = threading.Timer( + start_after, + ws.browser.place_bet, + (ws.events_predictions[event_id],), ) - if start_bet_status is True: - # place_bet_thread = threading.Timer(event.closing_bet_after(current_tmsp), ws.twitch.make_predictions, (ws.events_predictions[event_id],)) - start_after = calculate_start_after( - event.closing_bet_after(current_tmsp), - execution_time, - ) + place_bet_thread.daemon = True + place_bet_thread.start() - place_bet_thread = threading.Timer( - start_after, - ws.browser.place_bet, - (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:"}, - ) - else: - del ws.events_predictions[event_id] + logger.info( + f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", + extra={"emoji": ":alarm_clock:"}, + ) elif ( message.type == "event-updated" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index d682ad0..aaeafd4 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -35,10 +35,6 @@ def server_time(message_data): ) -def calculate_start_after(closing_bet_after, execution_time): - return round(max(1, closing_bet_after - execution_time), 2) - - # https://en.wikipedia.org/wiki/Cryptographic_nonce def create_nonce(length=30) -> str: nonce = "" From 5035232a5431475e7f87cce93ec747a22b75de55 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 1 Feb 2021 23:13:58 +0100 Subject: [PATCH 002/124] Get current top_points and if the current amount It's greater than top_points, bet top_poins -= random.uniform(1, 5) - stealth_mode #33 --- .../classes/entities/Bet.py | 31 ++++++++++++++----- TwitchChannelPointsMiner/utils.py | 4 +++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 52aa74e..f95e2b1 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -1,10 +1,11 @@ import copy import logging from enum import Enum, auto +from random import uniform from millify import millify -from TwitchChannelPointsMiner.utils import float_round +from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round logger = logging.getLogger(__name__) @@ -23,6 +24,7 @@ class BetSettings: percentage: int = None, percentage_gap: int = None, max_points: int = None, + stealth_mode: bool = None, ): self.strategy = strategy self.percentage = percentage @@ -34,9 +36,10 @@ class BetSettings: self.percentage = self.percentage if not None else 5 self.percentage_gap = self.percentage_gap if not None else 2 self.max_points = self.max_points if not None else 50000 + self.stealth_mode = self.stealth_mode if not None else False def __repr__(self): - return f"BetSettings(Strategy={self.strategy}, Percentage={self.percentage}, PercentageGap={self.percentage_gap}, MaxPoints={self.max_points})" + return f"BetSettings(Strategy={self.strategy}, Percentage={self.percentage}, PercentageGap={self.percentage_gap}, MaxPoints={self.max_points}, StealthMode={self.stealth_mode})" class Bet: @@ -54,6 +57,15 @@ class Bet: self.outcomes[0]["total_points"] = int(outcomes[0]["total_points"]) self.outcomes[1]["total_points"] = int(outcomes[1]["total_points"]) + outcomes[0]["top_predictors"] = sorted( + outcomes[0]["top_predictors"], key=lambda x: x["points"], reverse=True + ) + outcomes[1]["top_predictors"] = sorted( + outcomes[1]["top_predictors"], key=lambda x: x["points"], reverse=True + ) + self.outcomes[0]["top_points"] = outcomes[0]["top_predictors"]["points"] + self.outcomes[1]["top_points"] = outcomes[1]["top_predictors"]["points"] + self.total_users = ( self.outcomes[0]["total_users"] + self.outcomes[1]["total_users"] ) @@ -128,13 +140,18 @@ class Bet: ) if self.decision["choice"] is not None: - self.decision["id"] = ( - self.outcomes[0]["id"] - if self.decision["choice"] == "A" - else self.outcomes[1]["id"] - ) + index = char_decision_as_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]["top_points"] + ): + reduce_amount = uniform(1, 5) + self.decision["amount"] = ( + self.outcomes[index]["top_points"] - reduce_amount + ) return self.decision diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index aaeafd4..20aa135 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -144,3 +144,7 @@ def set_default_settings(settings, defaults): # Get the default values from Settings.streamer_settings settings = copy_values_if_none(settings, defaults) return settings + + +def char_decision_as_index(char): + return 0 if char == "A" else 1 From 64623ca4d4fdd201ce420eb42471cacdd1b84387 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 1 Feb 2021 23:15:53 +0100 Subject: [PATCH 003/124] Init top_points and keep in clear --- TwitchChannelPointsMiner/classes/entities/Bet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index f95e2b1..b7a6198 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -105,6 +105,7 @@ class Bet: if key not in [ "total_users", "total_points", + "top_points", "percentage_users", "odds", "odds_percentage", @@ -113,7 +114,7 @@ class Bet: "id", ]: del self.outcomes[index][key] - for key in ["percentage_users", "odds", "odds_percentage"]: + for key in ["percentage_users", "odds", "odds_percentage", "top_points"]: if key not in self.outcomes[index]: self.outcomes[index][key] = 0 From c737132aa2febc581f003bbe4a1b51c423e2ea28 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 2 Feb 2021 00:06:47 +0100 Subject: [PATCH 004/124] Apply filter on betting, skip some events if the criteria doesn't meet - #29 --- .../classes/TwitchBrowser.py | 64 ++++---- .../classes/entities/Bet.py | 147 +++++++++++++----- 2 files changed, 144 insertions(+), 67 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py index 1501330..b38e151 100644 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ b/TwitchChannelPointsMiner/classes/TwitchBrowser.py @@ -298,41 +298,49 @@ class TwitchBrowser: return False def place_bet(self, event: EventPrediction): + logger.info( + f"Place bet for {event} owned by {event.streamer}", + extra={"emoji": ":wrench:"}, + ) if event.status == "ACTIVE": - self.currently_is_betting = self.start_bet(event) - if event.box_fillable and self.currently_is_betting: - decision = event.bet.calculate(event.streamer.channel_points) - if decision["choice"] is not None: - selector_index = 1 if decision["choice"] == "A" else 2 - logger.info( - f"Decision: {event.bet.get_outcome(selector_index - 1)}", - extra={"emoji": ":wrench:"}, - ) - - try: + decision = event.bet.calculate(event.streamer.channel_points) + if event.bet.skip() is True: + logger.info(f"Skip betting for the event {event}") + logger.info(f"Skip settings {event.bet.settings.filter_condition}") + else: + self.currently_is_betting = self.start_bet(event) + if event.box_fillable and self.currently_is_betting: + if decision["choice"] is not None: + selector_index = 1 if decision["choice"] == "A" else 2 logger.info( - f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", + f"Decision: {event.bet.get_outcome(selector_index - 1)}", extra={"emoji": ":wrench:"}, ) - if ( - self.__send_text_on_bet( - event, selector_index, decision["amount"] - ) - is True - ): + + try: logger.info( - f"Going to place the bet for {event}", + f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", extra={"emoji": ":wrench:"}, ) - if self.__click_on_vote(event, selector_index) is True: - event.bet_placed = True - time.sleep(random.uniform(5, 10)) - except Exception: - logger.error("Exception raised", exc_info=True) - else: - logger.info( - f"Sorry, unable to complete the bet. Event box fillable: {event.box_fillable}, the browser is betting: {self.currently_is_betting}" - ) + if ( + self.__send_text_on_bet( + event, selector_index, decision["amount"] + ) + is True + ): + logger.info( + f"Going to place the bet for {event}", + extra={"emoji": ":wrench:"}, + ) + if self.__click_on_vote(event, selector_index) is True: + event.bet_placed = True + time.sleep(random.uniform(5, 10)) + except Exception: + logger.error("Exception raised", exc_info=True) + else: + logger.info( + f"Sorry, unable to complete the bet. Event box fillable: {event.box_fillable}, the browser is betting: {self.currently_is_betting}" + ) else: logger.info( f"Oh no! The event is not active anymore! Current status: {event.status}", diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index b7a6198..9aa47ef 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -17,6 +17,33 @@ class Strategy(Enum): SMART = auto() +class Condition(Enum): + GT = auto() + LT = auto() + GTE = auto() + LTE = auto() + + +class OutcomeKeys(object): + PERCENTAGE_USERS = "percentage_users" + ODDS_PERCENTAGE = "odds_percentage" + ODDS = "odds" + TOP_POINTS = "top_points" + TOTAL_USERS = "total_users" + TOTAL_POINTS = "total_points" + + +class FilterCondition(object): + def __init__(self, key=None, condition=None, value=None, decision=None): + self.key = key + self.condition = condition + self.value = value + self.decision = decision + + def __repr__(self): + return f"FilterCondition(Key={self.key}, Condition={self.condition}, Value={self.value}, Index={self.index})" + + class BetSettings: def __init__( self, @@ -25,11 +52,13 @@ class BetSettings: percentage_gap: int = None, max_points: int = None, stealth_mode: bool = None, + filter_condition: FilterCondition = None, ): self.strategy = strategy self.percentage = percentage self.percentage_gap = percentage_gap self.max_points = max_points + self.filter_condition = filter_condition def default(self): self.strategy = self.strategy if not None else Strategy.SMART @@ -52,41 +81,47 @@ class Bet: self.settings = settings def update_outcomes(self, outcomes): - self.outcomes[0]["total_users"] = int(outcomes[0]["total_users"]) - self.outcomes[1]["total_users"] = int(outcomes[1]["total_users"]) - self.outcomes[0]["total_points"] = int(outcomes[0]["total_points"]) - self.outcomes[1]["total_points"] = int(outcomes[1]["total_points"]) + 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] + ) - outcomes[0]["top_predictors"] = sorted( - outcomes[0]["top_predictors"], key=lambda x: x["points"], reverse=True - ) - outcomes[1]["top_predictors"] = sorted( - outcomes[1]["top_predictors"], key=lambda x: x["points"], reverse=True - ) - self.outcomes[0]["top_points"] = outcomes[0]["top_predictors"]["points"] - self.outcomes[1]["top_points"] = outcomes[1]["top_predictors"]["points"] + outcomes[index]["top_predictors"] = sorted( + outcomes[index]["top_predictors"], + key=lambda x: x["points"], + reverse=True, + ) + + top_points = outcomes[index]["top_predictors"]["points"] + self.outcomes[index][OutcomeKeys.TOP_POINTS] = top_points self.total_users = ( - self.outcomes[0]["total_users"] + self.outcomes[1]["total_users"] + self.outcomes[0][OutcomeKeys.TOTAL_USERS] + + self.outcomes[1][OutcomeKeys.TOTAL_USERS] ) self.total_points = ( - self.outcomes[0]["total_points"] + self.outcomes[1]["total_points"] + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] + + self.outcomes[1][OutcomeKeys.TOTAL_POINTS] ) if ( self.total_users > 0 - and self.outcomes[0]["total_points"] > 0 - and self.outcomes[1]["total_points"] > 0 + and self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 ): for index in range(0, len(self.outcomes)): - self.outcomes[index]["percentage_users"] = float_round( - (100 * self.outcomes[index]["total_users"]) / self.total_users + self.outcomes[index][OutcomeKeys.PERCENTAGE_USERS] = float_round( + (100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) + / self.total_users ) - self.outcomes[index]["odds"] = float_round( - self.total_points / self.outcomes[index]["total_points"] + self.outcomes[index][OutcomeKeys.ODDS] = float_round( + self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] ) - self.outcomes[index]["odds_percentage"] = float_round( - 100 / self.outcomes[index]["odds"] + self.outcomes[index][OutcomeKeys.ODDS_PERCENTAGE] = float_round( + 100 / self.outcomes[index][OutcomeKeys.ODDS] ) self.__clear_outcomes() @@ -96,48 +131,81 @@ class Bet: def get_outcome(self, index): outcome = self.outcomes[index] - return f"{outcome['title']} ({outcome['color']}), Points: {millify(outcome['total_points'])}, Users: {millify(outcome['total_users'])} ({outcome['percentage_users']}%), Odds: {outcome['odds']} ({outcome['odds_percentage']}%)" + 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 __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 [ - "total_users", - "total_points", - "top_points", - "percentage_users", - "odds", - "odds_percentage", + 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 ["percentage_users", "odds", "odds_percentage", "top_points"]: + 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 skip(self) -> bool: + if self.settings.filter_condition is not None: + key = self.settings.filter_condition.key + condition = self.settings.filter_condition.condition + value = self.settings.filter_condition.value + + compared_value = ( + (self.outcomes[0][key] + self.outcomes[1][key]) + if self.settings.filter_condition.decision is False + else self.outcomes[char_decision_as_index(self.decision["choice"])][key] + ) + logger.info( + f"Filter applied on this bet: {compared_value} {condition} {value}" + ) + if condition == Condition.GT: + if compared_value > value: + return True + elif condition == Condition.LT: + if compared_value < value: + return True + elif condition == Condition.GTE: + if compared_value >= value: + return True + elif condition == Condition.LTE: + if compared_value <= value: + return True + return False + 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("total_users") + self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS) elif self.settings.strategy == Strategy.HIGH_ODDS: - self.decision["choice"] = self.__return_choice("odds") + self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) elif self.settings.strategy == Strategy.PERCENTAGE: - self.decision["choice"] = self.__return_choice("odds_percentage") + self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS_PERCENTAGE) elif self.settings.strategy == Strategy.SMART: difference = abs( - self.outcomes[0]["percentage_users"] - - self.outcomes[1]["percentage_users"] + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] ) self.decision["choice"] = ( - self.__return_choice("odds") + self.__return_choice(OutcomeKeys.ODDS) if difference < self.settings.percentage_gap - else self.__return_choice("total_users") + else self.__return_choice(OutcomeKeys.TOTAL_USERS) ) if self.decision["choice"] is not None: @@ -149,10 +217,11 @@ class Bet: ) if ( self.settings.stealth_mode is True - and self.decision["amount"] >= self.outcomes[index]["top_points"] + and self.decision["amount"] + >= self.outcomes[index][OutcomeKeys.TOP_POINTS] ): reduce_amount = uniform(1, 5) self.decision["amount"] = ( - self.outcomes[index]["top_points"] - reduce_amount + self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount ) return self.decision From 97e6751dfe29346dd3d8872a483153f315b4b99d Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 2 Feb 2021 00:14:45 +0100 Subject: [PATCH 005/124] Code refactory :smile: --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- TwitchChannelPointsMiner/classes/TwitchBrowser.py | 13 +++++++++---- TwitchChannelPointsMiner/classes/TwitchLogin.py | 2 +- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 2 +- TwitchChannelPointsMiner/classes/entities/Bet.py | 4 ++-- .../classes/entities/EventPrediction.py | 2 +- .../classes/entities/Message.py | 2 +- .../classes/entities/PubsubTopic.py | 2 +- TwitchChannelPointsMiner/classes/entities/Raid.py | 2 +- TwitchChannelPointsMiner/classes/entities/Stream.py | 2 +- 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2d6a040..0dda307 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -26,7 +26,7 @@ from TwitchChannelPointsMiner.constants.twitch import API, CLIENT_ID, GQLOperati logger = logging.getLogger(__name__) -class Twitch: +class Twitch(object): def __init__(self, username, user_agent): cookies_path = os.path.join(Path().absolute(), "cookies") Path(cookies_path).mkdir(parents=True, exist_ok=True) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py index b38e151..488b926 100644 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ b/TwitchChannelPointsMiner/classes/TwitchBrowser.py @@ -16,7 +16,12 @@ from selenium.webdriver.support.ui import WebDriverWait from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction from TwitchChannelPointsMiner.constants.browser import Javascript, Selectors from TwitchChannelPointsMiner.constants.twitch import URL -from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_user_agent +from TwitchChannelPointsMiner.utils import ( + _millify, + bet_condition, + char_decision_as_index, + get_user_agent, +) logger = logging.getLogger(__name__) @@ -58,7 +63,7 @@ class BrowserSettings: ) -class TwitchBrowser: +class TwitchBrowser(object): def __init__( self, auth_token: str, @@ -311,7 +316,7 @@ class TwitchBrowser: self.currently_is_betting = self.start_bet(event) if event.box_fillable and self.currently_is_betting: if decision["choice"] is not None: - selector_index = 1 if decision["choice"] == "A" else 2 + selector_index = char_decision_as_index(decision["choice"]) + 1 logger.info( f"Decision: {event.bet.get_outcome(selector_index - 1)}", extra={"emoji": ":wrench:"}, @@ -334,7 +339,7 @@ class TwitchBrowser: ) if self.__click_on_vote(event, selector_index) is True: event.bet_placed = True - time.sleep(random.uniform(5, 10)) + time.sleep(random.uniform(3, 6)) except Exception: logger.error("Exception raised", exc_info=True) else: diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 74807fc..37929d1 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -15,7 +15,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import WrongCookiesException logger = logging.getLogger(__name__) -class TwitchLogin: +class TwitchLogin(object): def __init__(self, client_id, username, user_agent): self.client_id = client_id self.token = None diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index ae52595..7bbe869 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -17,7 +17,7 @@ from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_streamer logger = logging.getLogger(__name__) -class WebSocketsPool: +class WebSocketsPool(object): def __init__(self, twitch, browser, streamers, events_predictions): self.ws = None self.twitch = twitch diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 9aa47ef..b773655 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -44,7 +44,7 @@ class FilterCondition(object): return f"FilterCondition(Key={self.key}, Condition={self.condition}, Value={self.value}, Index={self.index})" -class BetSettings: +class BetSettings(object): def __init__( self, strategy: Strategy = None, @@ -71,7 +71,7 @@ class BetSettings: return f"BetSettings(Strategy={self.strategy}, Percentage={self.percentage}, PercentageGap={self.percentage_gap}, MaxPoints={self.max_points}, StealthMode={self.stealth_mode})" -class Bet: +class Bet(object): def __init__(self, outcomes: list, settings: BetSettings): self.outcomes = outcomes self.__clear_outcomes() diff --git a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py index 43da5f4..1751613 100644 --- a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py +++ b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py @@ -4,7 +4,7 @@ from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.utils import float_round -class EventPrediction: +class EventPrediction(object): def __init__( self, streamer: Streamer, diff --git a/TwitchChannelPointsMiner/classes/entities/Message.py b/TwitchChannelPointsMiner/classes/entities/Message.py index bb3daf2..2e0888c 100644 --- a/TwitchChannelPointsMiner/classes/entities/Message.py +++ b/TwitchChannelPointsMiner/classes/entities/Message.py @@ -3,7 +3,7 @@ import json from TwitchChannelPointsMiner.utils import server_time -class Message: +class Message(object): def __init__(self, data): self.topic, self.topic_user = data["topic"].split(".") diff --git a/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py index 05bf7a6..b7ccbb1 100644 --- a/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py +++ b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py @@ -1,4 +1,4 @@ -class PubsubTopic: +class PubsubTopic(object): def __init__(self, topic, user_id=None, streamer=None): self.topic = topic self.user_id = user_id diff --git a/TwitchChannelPointsMiner/classes/entities/Raid.py b/TwitchChannelPointsMiner/classes/entities/Raid.py index 0942e03..df8d680 100644 --- a/TwitchChannelPointsMiner/classes/entities/Raid.py +++ b/TwitchChannelPointsMiner/classes/entities/Raid.py @@ -1,4 +1,4 @@ -class Raid: +class Raid(object): def __init__(self, raid_id, target_login): self.raid_id = raid_id self.target_login = target_login diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index b5c58c8..88cef93 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -9,7 +9,7 @@ from TwitchChannelPointsMiner.constants.twitch import DROP_ID logger = logging.getLogger(__name__) -class Stream: +class Stream(object): def __init__(self): self.broadcast_id = None From 00dbadc14e0686195f77c7529ae643d294fd1379 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 2 Feb 2021 00:26:37 +0100 Subject: [PATCH 006/124] Fix betting after the last update. Update README with single settings (but not completed) --- README.md | 38 ++++++++++++++++++- .../classes/WebSocketsPool.py | 1 + .../classes/entities/Bet.py | 1 + 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b7cfb6..755db11 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,43 @@ Make sure to write the streamers array in order of priority from left to right. If the browser are currently betting or wait for more data It's impossible to interact with another event prediction from another streamer. -### Bet strategy +### Settings + +## LoggerSettings +- `save` +- `less` +- `console_level` +- `file_level` +- `emoji` +## BrowserSettings +- `timeout` +- `implicitly_wait` +- `max_attempts` +- `do_screenshot` +- `save_html` +- `show` +- `browser` +- `driver_path` +## StreamerSettings +- `make_predictions` +- `follow_raid` +- `claim_drops` +- `watch_streak` +- `bet` +## BetSettings +- `strategy` +- `percentage` +- `percentage_gap` +- `max_points` +- `stealth_mode` +- `filter_condition` +## FilterCondition +- `key` +- `condition` +- `value` +- `decision` + +## Bet strategy - **MOST_VOTED**: Select the option most voted based on users count - **HIGH_ODDS**: Select the option with the highest odds diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 7bbe869..b1a3260 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -201,6 +201,7 @@ class WebSocketsPool(object): is True ): # place_bet_thread = threading.Timer(event.closing_bet_after(current_tmsp), ws.twitch.make_predictions, (ws.events_predictions[event_id],)) + ws.events_predictions[event_id] = event start_after = event.closing_bet_after(current_tmsp) place_bet_thread = threading.Timer( diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index b773655..6588c0e 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -58,6 +58,7 @@ class BetSettings(object): self.percentage = percentage self.percentage_gap = percentage_gap self.max_points = max_points + self.stealth_mode = stealth_mode self.filter_condition = filter_condition def default(self): From ecb6adb15c8aca4083f1c841adab61e585cfabc7 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 2 Feb 2021 11:30:30 +0100 Subject: [PATCH 007/124] Update example.py --- example.py | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/example.py b/example.py index 3c86db0..d2c1244 100644 --- a/example.py +++ b/example.py @@ -3,35 +3,41 @@ import logging from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings -from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings +from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", - claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup + claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup logger_settings=LoggerSettings( - save=True, # If you want to save logs in file (suggested) - console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info) - 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 to print emoji. Set to false if you have a problem - less=False # If you think that the logs are too much verborse set this to True + save=True, # If you want to save logs in file (suggested) + console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info) + 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 to print emoji. Set to false if you have a problem + less=False # If you think that the logs are too much verborse set this to True ), browser_settings=BrowserSettings( - browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser - show=False, # Show the browser during bet else headless mode - do_screenshot=False, # Do screenshot during the bet + browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser + show=False, # Show the browser during bet else headless mode + do_screenshot=False, # Do screenshot during the bet ), 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 - watch_streak=True, # If a streamer go online change the priotiry of streamers array and catch the watch screak. Issue #11 + 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 + watch_streak=True, # If a streamer go online change the priotiry of streamers array and catch the watch screak. Issue #11 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 stragegy) - max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value + 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 stragegy) + max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value + filter_condition=FilterCondition( + key=OutcomeKeys.TOTAL_USERS, + condition=Condition.LTE, + value=800, + decision=False + ) ) ) ) @@ -46,11 +52,11 @@ twitch_miner = TwitchChannelPointsMiner( 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 , percentage_gap=20 , max_points=234 ) )), - Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , percentage_gap=20 , max_points=1234 ) )), - Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , percentage_gap=30 , max_points=50000 ) )), - 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.ODDS , percentage=7 , percentage_gap=20 , max_points=90 ) )), + Streamer("streamer-username01", settings=StreamerSettings(make_predictions=True , follow_raid=False , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), + Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , percentage_gap=20 , max_points=1234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), + Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , percentage_gap=30 , max_points=50000 , filter_condition=FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), + 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.ODDS , percentage=7 , percentage_gap=20 , max_points=90 , filter_condition=FilterCondition(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), Streamer("streamer-username06"), Streamer("streamer-username07"), Streamer("streamer-username08"), From 4cd6a31a80258aff878fe83f15e51735ec0eee38 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 11:37:58 +0100 Subject: [PATCH 008/124] Fix underline in badge caused by ahref and space --- README.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 714777a..e4bc10e 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,12 @@ ![Banner](./assets/banner.png)

- - License - - - Python3 - - - PRsWelcome - - - GitHub Repo stars - - - GitHub closed issues - - - GitHub last commit - +License +Python3 +PRsWelcome +GitHub Repo stars +GitHub closed issues +GitHub last commit

**Credits** From 7cbc834f80432f31a4c66b9b19e098b732bfecb1 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 12:00:11 +0100 Subject: [PATCH 009/124] Do not change prediction in parent settings --- TwitchChannelPointsMiner/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index d682ad0..4c93329 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -1,6 +1,7 @@ import platform import re import time +from copy import deepcopy from datetime import datetime, timezone from random import randrange @@ -142,7 +143,7 @@ def copy_values_if_none(settings, defaults): def set_default_settings(settings, defaults): # If no settings was provided use the default settings ... if settings is None: - settings = defaults + settings = deepcopy(defaults) else: # If settings was provided but maybe are only partial set # Get the default values from Settings.streamer_settings From 92648fa6db1ceab42b5cfa6af8aad3499d30ffc3 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 12:02:38 +0100 Subject: [PATCH 010/124] According to #41 we can use a random hex of 32 chars as transactionID --- .../TwitchChannelPointsMiner.py | 17 +------- TwitchChannelPointsMiner/classes/Twitch.py | 3 +- .../classes/TwitchWebSocket.py | 1 - .../classes/WebSocketsPool.py | 40 +++++-------------- TwitchChannelPointsMiner/utils.py | 21 ---------- 5 files changed, 14 insertions(+), 68 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 552468b..fdc32b7 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -19,10 +19,7 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import ( from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Twitch import Twitch -from TwitchChannelPointsMiner.classes.TwitchBrowser import ( - BrowserSettings, - TwitchBrowser, -) +from TwitchChannelPointsMiner.classes.TwitchBrowser import BrowserSettings from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers from TwitchChannelPointsMiner.utils import ( @@ -175,14 +172,6 @@ class TwitchChannelPointsMiner: make_predictions = at_least_one_value_in_settings_is( self.streamers, "make_predictions", True ) - # We need a browser to make predictions / bet - if make_predictions is True: - self.twitch_browser = TwitchBrowser( - self.twitch.twitch_login.get_auth_token(), - self.session_id, - settings=Settings.browser, - ) - self.twitch_browser.init() self.minute_watcher_thread = threading.Thread( target=self.twitch.send_minute_watched_events, @@ -197,7 +186,6 @@ class TwitchChannelPointsMiner: self.ws_pool = WebSocketsPool( twitch=self.twitch, - browser=self.twitch_browser, streamers=self.streamers, events_predictions=self.events_predictions, ) @@ -257,9 +245,6 @@ class TwitchChannelPointsMiner: def end(self, signum, frame): logger.info("CTRL+C Detected! Please wait just a moments!") - if self.twitch_browser is not None: - self.twitch_browser.browser.quit() - self.running = self.twitch.running = False self.ws_pool.end() diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2d6a040..b6b7200 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -11,6 +11,7 @@ import random import re import time from pathlib import Path +from secrets import token_hex import requests @@ -204,7 +205,7 @@ class Twitch: "eventID": event.event_id, "outcomeID": decision["id"], "points": decision["amount"], - "transactionID": "412118d3********79ac856", # How we can calculate this? + "transactionID": token_hex(16), } } return self.post_gql_request(json_data) diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index 4edecec..b2d6b53 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -39,7 +39,6 @@ class TwitchWebSocket(WebSocketApp): self.pending_topics = [] self.twitch = parent_pool.twitch - self.browser = parent_pool.browser self.streamers = parent_pool.streamers self.events_predictions = parent_pool.events_predictions diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 1a46173..9bfbfd0 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -12,21 +12,15 @@ from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants.twitch import WEBSOCKET -from TwitchChannelPointsMiner.utils import ( - _millify, - bet_condition, - calculate_start_after, - get_streamer_index, -) +from TwitchChannelPointsMiner.utils import _millify, get_streamer_index logger = logging.getLogger(__name__) class WebSocketsPool: - def __init__(self, twitch, browser, streamers, events_predictions): + def __init__(self, twitch, streamers, events_predictions): self.ws = None self.twitch = twitch - self.browser = browser self.streamers = streamers self.events_predictions = events_predictions @@ -198,30 +192,20 @@ class WebSocketsPool: if ( ws.streamers[streamer_index].is_online and event.closing_bet_after(current_tmsp) > 0 - and bet_condition( - ws.browser, - event, - logger, - ) - is True ): - ws.events_predictions[event_id] = event - ( - start_bet_status, - execution_time, - ) = ws.browser.start_bet( - ws.events_predictions[event_id] - ) - if start_bet_status is True: - # place_bet_thread = threading.Timer(event.closing_bet_after(current_tmsp), ws.twitch.make_predictions, (ws.events_predictions[event_id],)) - start_after = calculate_start_after( - event.closing_bet_after(current_tmsp), - execution_time, + if event.streamer.viewer_is_mod is True: + logger.info( + f"Sorry, you are moderator of {event.streamer}, so you can't bet!" + ) + else: + ws.events_predictions[event_id] = event + start_after = event.closing_bet_after( + current_tmsp ) place_bet_thread = threading.Timer( start_after, - ws.browser.place_bet, + ws.twitch.make_predictions, (ws.events_predictions[event_id],), ) place_bet_thread.daemon = True @@ -231,8 +215,6 @@ class WebSocketsPool: f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", extra={"emoji": ":alarm_clock:"}, ) - else: - del ws.events_predictions[event_id] elif ( message.type == "event-updated" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index d682ad0..81382bd 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -35,10 +35,6 @@ def server_time(message_data): ) -def calculate_start_after(closing_bet_after, execution_time): - return round(max(1, closing_bet_after - execution_time), 2) - - # https://en.wikipedia.org/wiki/Cryptographic_nonce def create_nonce(length=30) -> str: nonce = "" @@ -54,23 +50,6 @@ def create_nonce(length=30) -> str: return nonce -def bet_condition(twitch_browser, event, logger) -> bool: - if twitch_browser.currently_is_betting is True: - logger.info( - f"Sorry, unable to start {event}, the browser is currently betting on another event!" - ) - return False - elif twitch_browser.browser.current_url != "about:blank": - logger.info( - "Sorry, but the browser is not currently on 'about:blank' screen. Unable to start bet!" - ) - return False - elif event.streamer.viewer_is_mod is True: - logger.info(f"Sorry, you are moderator of {event.streamer}, so you can't bet!") - return False - return True - - def get_user_agent(browser) -> str: try: return USER_AGENTS[platform.system()][ From 0adf168dea0427e2ac6804de6015f1ac7811e234 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 13:07:35 +0100 Subject: [PATCH 011/124] Display prediction gain instead of points won & merge prediction refund into prediction --- .gitignore | 3 ++ .../classes/WebSocketsPool.py | 30 +++++++++++++------ .../classes/entities/Streamer.py | 7 +++-- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 0a444db..0d8276a 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,9 @@ dmypy.json # Cython debug symbols cython_debug/ +# PyCharm +.idea/ + # Custom files run.py chromedriver* diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 1a46173..c3b8acc 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -251,23 +251,35 @@ class WebSocketsPool: elif message.topic == "predictions-user-v1": event_id = message.data["prediction"]["event_id"] if event_id in ws.events_predictions: - if message.type == "prediction-result": + event_prediction = ws.events_predictions[event_id] + if message.type == "prediction-result" and event_prediction.bet_confirmed: event_result = message.data["prediction"]["result"] + result_type = event_result['type'] + points_placed = event_prediction.bet.decision["amount"] + points_won = event_result["points_won"] if event_result["points_won"] or result_type == "REFUND" else 0 + points_gained = points_won - points_placed if result_type != "REFUND" else 0 logger.info( - f"{ws.events_predictions[event_id]} - Result: {event_result['type']}, Points won: {_millify(event_result['points_won']) if event_result['points_won'] else 0}", + f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {_millify(points_gained)}", extra={"emoji": ":bar_chart:"}, ) - points_won = ( - event_result["points_won"] - if event_result["points_won"] - else 0 - ) ws.events_predictions[event_id].final_result = { "type": event_result["type"], - "won": points_won, + "points_won": points_won, + "gained": points_gained } + ws.streamers[streamer_index].update_history( + "PREDICTION-TEST", points_gained + ) + + # Remove duplicate history records from previous message sent in community-points-user-v1 + if result_type == "REFUND": + logger.info("REMOVE REFUND FROM POINTS MSG") # TODO remove + ws.streamers[streamer_index].update_history("REFUND", -points_placed, counter=-1, create_if_missing=False) + else: + logger.info("REMOVE PREDICTION FROM POINTS MSG") # TODO remove + ws.streamers[streamer_index].update_history("PREDICTION", -points_won, counter=-1, create_if_missing=False) elif message.type == "prediction-made": - ws.events_predictions[event_id].bet_confirmed = True + event_prediction.bet_confirmed = True elif message.topic == "user-drop-events": if message.type == "drop-progress": diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 6eef727..9ecbfe8 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -87,13 +87,16 @@ class Streamer(object): [ f"{key}({self.history[key]['counter']} times, {_millify(self.history[key]['amount'])} gained)" for key in self.history + if self.history[key]['counter'] != 0 ] ) - def update_history(self, reason_code, earned): + def update_history(self, reason_code, earned, counter=1, create_if_missing=True): if reason_code not in self.history: + if not create_if_missing: + return self.history[reason_code] = {"counter": 0, "amount": 0} - self.history[reason_code]["counter"] += 1 + self.history[reason_code]["counter"] += counter self.history[reason_code]["amount"] += earned if reason_code == "WATCH_STREAK": From 51c0d6dcfa43b1bd3af3989c19e99595f593e790 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 13:12:25 +0100 Subject: [PATCH 012/124] According to #42 create and manage an array of ws in pool class. Related also to #39. --- .../TwitchChannelPointsMiner.py | 13 +++-- .../classes/TwitchWebSocket.py | 6 +- .../classes/WebSocketsPool.py | 57 +++++++++++-------- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 552468b..58ec957 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -248,11 +248,14 @@ class TwitchChannelPointsMiner: while self.running: time.sleep(random.uniform(20, 60)) # Do an external control for WebSocket. Check if the thread is running - if self.ws_pool.ws.elapsed_last_ping() > 5: - logger.info( - "The last ping was sent more than 5 minutes ago. Reconnecting to the WebSocket..." - ) - WebSocketsPool.handle_websocket_reconnection(self.ws_pool.ws) + for index in range(0, len(self.ws_pool.ws)): + if self.ws_pool.ws[index].elapsed_last_ping() > 5: + logger.info( + f"#{index} - The last ping was sent more than 5 minutes ago. Reconnecting to the WebSocket..." + ) + WebSocketsPool.handle_websocket_reconnection( + self.ws_pool.ws[index] + ) def end(self, signum, frame): logger.info("CTRL+C Detected! Please wait just a moments!") diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index 4edecec..9d99242 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -10,6 +10,10 @@ logger = logging.getLogger(__name__) class TwitchWebSocket(WebSocketApp): + def __init__(self, index, *args, **kw): + super().__init__(*args, **kw) + self.index = index + def listen(self, topic, auth_token=None): data = {"topics": [str(topic)]} if topic.is_user_topic() and auth_token is not None: @@ -24,7 +28,7 @@ class TwitchWebSocket(WebSocketApp): def send(self, request): request_str = json.dumps(request, separators=(",", ":")) - logger.debug(f"Send: {request_str}") + logger.debug(f"#{self.index} - Send: {request_str}") super().send(request_str) def reset(self, parent_pool): diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 1a46173..dd5cc13 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -24,7 +24,7 @@ logger = logging.getLogger(__name__) class WebSocketsPool: def __init__(self, twitch, browser, streamers, events_predictions): - self.ws = None + self.ws: list = [] self.twitch = twitch self.browser = browser self.streamers = streamers @@ -38,32 +38,40 @@ class WebSocketsPool: """ def submit(self, topic): - if self.ws is None or len(self.ws.topics) >= 50: - self.create_new_websocket() + if self.ws == [] or len(self.ws[len(self.ws) - 1].topics) >= 50: + self.append_new_websocket() - self.ws.topics.append(topic) + self.ws[len(self.ws) - 1].topics.append(topic) - if not self.ws.is_opened: - self.ws.pending_topics.append(topic) + if not self.ws[len(self.ws) - 1].is_opened: + self.ws[len(self.ws) - 1].pending_topics.append(topic) else: - self.ws.listen(topic, self.twitch.twitch_login.get_auth_token()) + self.ws[len(self.ws) - 1].listen( + topic, self.twitch.twitch_login.get_auth_token() + ) - def create_new_websocket(self): - self.ws = TwitchWebSocket( - WEBSOCKET, - on_message=WebSocketsPool.on_message, - on_open=WebSocketsPool.on_open, - on_close=WebSocketsPool.handle_websocket_reconnection, + def append_new_websocket(self): + self.ws.append( + TwitchWebSocket( + index=len(self.ws), + url=WEBSOCKET, + on_message=WebSocketsPool.on_message, + on_open=WebSocketsPool.on_open, + on_close=WebSocketsPool.handle_websocket_reconnection, + ) ) - self.ws.reset(self) + self.ws[len(self.ws) - 1].reset(self) - self.thread_ws = threading.Thread(target=lambda: self.ws.run_forever()) + self.thread_ws = threading.Thread( + target=lambda: self.ws[len(self.ws) - 1].run_forever() + ) self.thread_ws.daemon = True self.thread_ws.start() def end(self): - self.ws.keep_running = False - self.ws.close() + for index in range(0, len(self.ws)): + self.ws[index].keep_running = False + self.ws[index].close() @staticmethod def on_open(ws): @@ -79,7 +87,7 @@ class WebSocketsPool: if ws.elapsed_last_pong() > 15 and ws.is_reconneting is False: logger.info( - "The last pong was received more than 15 minutes ago. Reconnect the WebSocket" + f"#{ws.index} - The last pong was received more than 15 minutes ago. Reconnect the WebSocket" ) ws.keep_running = True ws.is_reconneting = True @@ -93,18 +101,19 @@ class WebSocketsPool: def handle_websocket_reconnection(ws): ws.is_closed = True if ws.keep_running is True: - logger.info("Reconnecting to Twitch PubSub server in 60 seconds") + logger.info( + f"#{ws.index} - Reconnecting to Twitch PubSub server in 60 seconds" + ) time.sleep(60) self = ws.parent_pool - if self.ws == ws: - self.ws = None + self.ws[ws.index] = None for topic in ws.topics: self.submit(topic) @staticmethod def on_message(ws, message): - logger.debug(f"Received: {message.strip()}") + logger.debug(f"#{ws.index} - Received: {message.strip()}") response = json.loads(message) if response["type"] == "MESSAGE": @@ -307,7 +316,9 @@ class WebSocketsPool: raise RuntimeError(f"Error while trying to listen for a topic: {response}") elif response["type"] == "RECONNECT": - logger.info(f"Reconnection required and keep running is: {ws.keep_running}") + logger.info( + f"#{ws.index} - Reconnection required and keep running is: {ws.keep_running}" + ) ws.is_reconneting = True WebSocketsPool.handle_websocket_reconnection(ws) From e92f4a58aa341e271968525b9723bde99a283df2 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 13:25:48 +0100 Subject: [PATCH 013/124] Display gained as "+x" if x is positive --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index c3b8acc..4cf782e 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -258,8 +258,9 @@ class WebSocketsPool: points_placed = event_prediction.bet.decision["amount"] points_won = event_result["points_won"] if event_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 "" logger.info( - f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {_millify(points_gained)}", + f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {points_prefix}{_millify(points_gained)}", extra={"emoji": ":bar_chart:"}, ) ws.events_predictions[event_id].final_result = { From b7c1a1de065fc6e6edaa864388417985331bb61f Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 13:31:08 +0100 Subject: [PATCH 014/124] Fix lint report --- .../classes/WebSocketsPool.py | 22 ++++++++++++++----- .../classes/entities/Streamer.py | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 4cf782e..a4c3070 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -252,12 +252,24 @@ class WebSocketsPool: 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: + if ( + message.type == "prediction-result" + and event_prediction.bet_confirmed + ): event_result = message.data["prediction"]["result"] - result_type = event_result['type'] + result_type = event_result["type"] points_placed = event_prediction.bet.decision["amount"] - points_won = event_result["points_won"] if event_result["points_won"] or result_type == "REFUND" else 0 - points_gained = points_won - points_placed if result_type != "REFUND" else 0 + points_won = ( + event_result["points_won"] + if event_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 "" logger.info( f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {points_prefix}{_millify(points_gained)}", @@ -266,7 +278,7 @@ class WebSocketsPool: ws.events_predictions[event_id].final_result = { "type": event_result["type"], "points_won": points_won, - "gained": points_gained + "gained": points_gained, } ws.streamers[streamer_index].update_history( "PREDICTION-TEST", points_gained diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 9ecbfe8..301aa57 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -87,7 +87,7 @@ class Streamer(object): [ f"{key}({self.history[key]['counter']} times, {_millify(self.history[key]['amount'])} gained)" for key in self.history - if self.history[key]['counter'] != 0 + if self.history[key]["counter"] != 0 ] ) From 6565b714d9aacebdd862c72717d51b378a33f611 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 13:33:10 +0100 Subject: [PATCH 015/124] Fix lint report again --- .../classes/WebSocketsPool.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index a4c3070..c3c55f2 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -286,11 +286,25 @@ class WebSocketsPool: # Remove duplicate history records from previous message sent in community-points-user-v1 if result_type == "REFUND": - logger.info("REMOVE REFUND FROM POINTS MSG") # TODO remove - ws.streamers[streamer_index].update_history("REFUND", -points_placed, counter=-1, create_if_missing=False) + logger.info( + "REMOVE REFUND FROM POINTS MSG" + ) # TODO remove + ws.streamers[streamer_index].update_history( + "REFUND", + -points_placed, + counter=-1, + create_if_missing=False + ) else: - logger.info("REMOVE PREDICTION FROM POINTS MSG") # TODO remove - ws.streamers[streamer_index].update_history("PREDICTION", -points_won, counter=-1, create_if_missing=False) + logger.info( + "REMOVE PREDICTION FROM POINTS MSG" + ) # TODO remove + ws.streamers[streamer_index].update_history( + "PREDICTION", + -points_won, + counter=-1, + create_if_missing=False + ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True From 91a7bda0da2ee48c3dba595e193f42833aeaa05b Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 13:34:17 +0100 Subject: [PATCH 016/124] =?UTF-8?q?Fix=20lint=20report=20again=20?= =?UTF-8?q?=F0=9F=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index c3c55f2..8c14077 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -293,7 +293,7 @@ class WebSocketsPool: "REFUND", -points_placed, counter=-1, - create_if_missing=False + create_if_missing=False, ) else: logger.info( @@ -303,7 +303,7 @@ class WebSocketsPool: "PREDICTION", -points_won, counter=-1, - create_if_missing=False + create_if_missing=False, ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True From 08b07a973038849e73bcd267f3a05af63ef24f81 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 13:38:04 +0100 Subject: [PATCH 017/124] is False, instead of Not condition --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index dd5cc13..b11422b 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -43,7 +43,7 @@ class WebSocketsPool: self.ws[len(self.ws) - 1].topics.append(topic) - if not self.ws[len(self.ws) - 1].is_opened: + if self.ws[len(self.ws) - 1].is_opened is False: self.ws[len(self.ws) - 1].pending_topics.append(topic) else: self.ws[len(self.ws) - 1].listen( From 576b9da12a68b1238911a72a40e0107e615c3913 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 14:23:55 +0100 Subject: [PATCH 018/124] Fix filter condition and top_points --- .../classes/entities/Bet.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 6588c0e..6b5b7b7 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -89,15 +89,16 @@ class Bet(object): self.outcomes[index][OutcomeKeys.TOTAL_POINTS] = int( outcomes[index][OutcomeKeys.TOTAL_POINTS] ) - - outcomes[index]["top_predictors"] = sorted( - outcomes[index]["top_predictors"], - key=lambda x: x["points"], - reverse=True, - ) - - top_points = outcomes[index]["top_predictors"]["points"] - self.outcomes[index][OutcomeKeys.TOP_POINTS] = top_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 self.total_users = ( self.outcomes[0][OutcomeKeys.TOTAL_USERS] @@ -174,7 +175,7 @@ class Bet(object): else self.outcomes[char_decision_as_index(self.decision["choice"])][key] ) logger.info( - f"Filter applied on this bet: {compared_value} {condition} {value}" + f"Filter applied on this bet. Current {key} is {compared_value}, must be {condition} {value}" ) if condition == Condition.GT: if compared_value > value: From 6070d9b65ea9e24a22030fc551964dc700c04931 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 14:35:30 +0100 Subject: [PATCH 019/124] Fix bet skip - If condition is satisfiend don't skip, else true --- TwitchChannelPointsMiner/classes/entities/Bet.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 6b5b7b7..3fe9d55 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -177,19 +177,22 @@ class Bet(object): logger.info( f"Filter applied on this bet. Current {key} is {compared_value}, must be {condition} {value}" ) + # Check if condition is satisfied if condition == Condition.GT: if compared_value > value: - return True + return False elif condition == Condition.LT: if compared_value < value: - return True + return False elif condition == Condition.GTE: if compared_value >= value: - return True + return False elif condition == Condition.LTE: if compared_value <= value: - return True - return False + return False + return True # Else skip the bet + else: + return False # Default don't skip the bet def calculate(self, balance: int) -> dict: self.decision = {"choice": None, "amount": 0, "id": None} From c9a2e8ffcd84f30dacca544ce464aca4f4d67b95 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 14:44:05 +0100 Subject: [PATCH 020/124] Fix __repr__ --- TwitchChannelPointsMiner/classes/entities/Bet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 3fe9d55..4bb058d 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -41,7 +41,7 @@ class FilterCondition(object): self.decision = decision def __repr__(self): - return f"FilterCondition(Key={self.key}, Condition={self.condition}, Value={self.value}, Index={self.index})" + return f"FilterCondition(Key={self.key}, Condition={self.condition}, Value={self.value}, Decision={self.decision})" class BetSettings(object): From 1b6b7090b4e25a4d71ddd1e733db79c81f17f764 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 14:55:03 +0100 Subject: [PATCH 021/124] Prevent repeat of Operation=Operation.LTE => Operation=LTE - Same for Strategy and Browser --- TwitchChannelPointsMiner/classes/TwitchBrowser.py | 3 +++ TwitchChannelPointsMiner/classes/entities/Bet.py | 6 ++++++ TwitchChannelPointsMiner/utils.py | 4 +--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py index 488b926..2e12a31 100644 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ b/TwitchChannelPointsMiner/classes/TwitchBrowser.py @@ -30,6 +30,9 @@ class Browser(Enum): CHROME = auto() FIREFOX = auto() + def __str__(self): + return self.name + class BrowserSettings: def __init__( diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 4bb058d..655a99f 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -16,6 +16,9 @@ class Strategy(Enum): PERCENTAGE = auto() SMART = auto() + def __str__(self): + return self.name + class Condition(Enum): GT = auto() @@ -23,6 +26,9 @@ class Condition(Enum): GTE = auto() LTE = auto() + def __str__(self): + return self.name + class OutcomeKeys(object): PERCENTAGE_USERS = "percentage_users" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 20aa135..358ee0c 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -69,9 +69,7 @@ def bet_condition(twitch_browser, event, logger) -> bool: def get_user_agent(browser) -> str: try: - return USER_AGENTS[platform.system()][ - browser.name if type(browser) != str else browser - ] + return USER_AGENTS[platform.system()][browser] except KeyError: return USER_AGENTS["Linux"]["FIREFOX"] From a1d472f2fa9af6f26eadb897f532a7885a398561 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 14:57:15 +0100 Subject: [PATCH 022/124] Get the latest element from array with [-1] instead of len(item) - 1, thanks for the hit! @RakSrinaNa --- .../classes/WebSocketsPool.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index b11422b..925e772 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -38,17 +38,15 @@ class WebSocketsPool: """ def submit(self, topic): - if self.ws == [] or len(self.ws[len(self.ws) - 1].topics) >= 50: + if self.ws == [] or len(self.ws[-1].topics) >= 50: self.append_new_websocket() - self.ws[len(self.ws) - 1].topics.append(topic) + self.ws[-1].topics.append(topic) - if self.ws[len(self.ws) - 1].is_opened is False: - self.ws[len(self.ws) - 1].pending_topics.append(topic) + if self.ws[-1].is_opened is False: + self.ws[-1].pending_topics.append(topic) else: - self.ws[len(self.ws) - 1].listen( - topic, self.twitch.twitch_login.get_auth_token() - ) + self.ws[-1].listen(topic, self.twitch.twitch_login.get_auth_token()) def append_new_websocket(self): self.ws.append( @@ -60,11 +58,9 @@ class WebSocketsPool: on_close=WebSocketsPool.handle_websocket_reconnection, ) ) - self.ws[len(self.ws) - 1].reset(self) + self.ws[-1].reset(self) - self.thread_ws = threading.Thread( - target=lambda: self.ws[len(self.ws) - 1].run_forever() - ) + self.thread_ws = threading.Thread(target=lambda: self.ws[-1].run_forever()) self.thread_ws.daemon = True self.thread_ws.start() From 29f3a97a17e0b96a37276cad77be8b88d6a2eb9f Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 15:29:09 +0100 Subject: [PATCH 023/124] Readme update 1 with LoggerSettings table. I'll continue later in another PC --- README.md | 34 ++++++++++++++++++---------------- example.py | 8 ++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index da43a58..c5d2ed5 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,10 @@ twitch_miner = TwitchChannelPointsMiner( percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value filter_condition=FilterCondition( - key=OutcomeKeys.TOTAL_USERS, - condition=Condition.LTE, - value=800, - decision=False + key=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + condition=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value + value=800, # + decision=False # If the filter should apply to the decision or on the sum ) ) ) @@ -240,15 +240,17 @@ Make sure to write the streamers array in order of priority from left to right. If the browser are currently betting or wait for more data It's impossible to interact with another event prediction from another streamer. -### Settings +## Settings -## LoggerSettings -- `save` -- `less` -- `console_level` -- `file_level` -- `emoji` -## BrowserSettings +### LoggerSettings +| Key | Type | Value allowed | Default | Description | +|----------------- |----------------- |--------------- |-------------------------------- |-------------------------------------------------------------------------------------- | +| `save` | Bool | True, False | True | If you want to save logs in file (suggested) | +| `less` | Bool | True, False | False | Reduce the logging format and message verbosity | +| `console_level` | int / logging.* | | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | +| `file_level` | int / logging.* | | logging.DEBUG | Level of logs in file save - If you think the log file it's too big use logging.INFO | +| `emoji` | Bool | True, False | For Windows is False else True | On Windows we have a problem to print emoji. Set to false if you have a problem | +### BrowserSettings - `timeout` - `implicitly_wait` - `max_attempts` @@ -257,26 +259,26 @@ If the browser are currently betting or wait for more data It's impossible to in - `show` - `browser` - `driver_path` -## StreamerSettings +### StreamerSettings - `make_predictions` - `follow_raid` - `claim_drops` - `watch_streak` - `bet` -## BetSettings +### BetSettings - `strategy` - `percentage` - `percentage_gap` - `max_points` - `stealth_mode` - `filter_condition` -## FilterCondition +### FilterCondition - `key` - `condition` - `value` - `decision` -## Bet strategy +### Bet strategy - **MOST_VOTED**: Select the option most voted based on users count - **HIGH_ODDS**: Select the option with the highest odds diff --git a/example.py b/example.py index 8860101..ab11ead 100644 --- a/example.py +++ b/example.py @@ -33,10 +33,10 @@ twitch_miner = TwitchChannelPointsMiner( percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value filter_condition=FilterCondition( - key=OutcomeKeys.TOTAL_USERS, - condition=Condition.LTE, - value=800, - decision=False + key=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + condition=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value + value=800, # + decision=False # If the filter should apply to the decision or on the sum ) ) ) From f7b74bffa8e302163ad698a2e449eba1939a4292 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 16:48:34 +0100 Subject: [PATCH 024/124] Use table in README for explain better all the settings available --- README.md | 72 +++++++++++-------- .../classes/entities/Bet.py | 2 +- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c5d2ed5..93733ac 100644 --- a/README.md +++ b/README.md @@ -245,40 +245,41 @@ If the browser are currently betting or wait for more data It's impossible to in ### LoggerSettings | Key | Type | Value allowed | Default | Description | |----------------- |----------------- |--------------- |-------------------------------- |-------------------------------------------------------------------------------------- | -| `save` | Bool | True, False | True | If you want to save logs in file (suggested) | -| `less` | Bool | True, False | False | Reduce the logging format and message verbosity | +| `save` | bool | True, False | True | If you want to save logs in file (suggested) | +| `less` | bool | True, False | False | Reduce the logging format and message verbosity #10 | | `console_level` | int / logging.* | | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | | `file_level` | int / logging.* | | logging.DEBUG | Level of logs in file save - If you think the log file it's too big use logging.INFO | -| `emoji` | Bool | True, False | For Windows is False else True | On Windows we have a problem to print emoji. Set to false if you have a problem | +| `emoji` | bool | True, False | For Windows is False else True | On Windows we have a problem to print emoji. Set to false if you have a problem | ### BrowserSettings -- `timeout` -- `implicitly_wait` -- `max_attempts` -- `do_screenshot` -- `save_html` -- `show` -- `browser` -- `driver_path` +| Key | Type | Value allowed | Default | Description | +|------------------- |--------- |----------------- |--------- |------------------------------------------------------------------------------------------------- | +| `timeout` | float | Positive | 10 | If no element was found by Selenium raise exception after timeouts. Increase on slow connection | +| `implicitly_wait` | int | Positive | 5 | Wait x seconds after continue Selenium execution | +| `max_attempts` | int | Positive | 3 | Number of max attempt for place bet | +| `do_screenshot` | bool | True, False | False | Save screenshot before/after do some Selenium action - Help debug | +| `save_html` | bool | True, False | False | Save html content before/after do some Selenium action - Help debug | +| `show` | bool | True, False | True | Choose if you want to see or not the browser - Help debug | +| `browser` | Browser | CHROME, FIREFOX | FIREFOX | Choose your favourite browser | +| `driver_path` | str | /path/ | None | Write the path of chromedriver or geckodriver | ### StreamerSettings -- `make_predictions` -- `follow_raid` -- `claim_drops` -- `watch_streak` -- `bet` +| Key | Type | Value allowed | Default | Description | +|-------------------- |------------- |--------------- |-------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True, False | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True, False | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True, False | True | If this values is True the script will increase the watch-time for the current game. With this you are able to claim the drops from Twitch Inventory #21 | +| `watch_streak` | bool | True, False | True | Choose if you want to change priority for this streamers and try to catch the Watch Streak event #11 | +| `bet` | BetSettings | | Default values for BetSettings | Rules to follow for the bet | ### BetSettings -- `strategy` -- `percentage` -- `percentage_gap` -- `max_points` -- `stealth_mode` -- `filter_condition` -### FilterCondition -- `key` -- `condition` -- `value` -- `decision` +| Key | Type | Value allowed | Default | Description | +|-------------------- |----------------- |------------------------------------------ |--------- |---------------------------------------------------------------------------------------------------------------- | +| `strategy` | Strategy | MOST_VOTED, HIGH_ODDS, PERCENTAGE, SMART | SMART | Choose your strategy! See above for more info | +| `percentage` | int | Positive | 5 | Place the x% of your channel points | +| `percentage_gap` | int | Positive | 20 | Gap difference between outcomesA and outcomesB (for SMART stragegy) | +| `max_points` | int | Positive | 50000 | If the x percentage of your channel points is GT bet_max_points set this value | +| `stealth_mode` | bool | True, False | False | If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 | +| `filter_condition` | FilterCondition | | None | Based on this filter the script will skip some bet #29 | -### Bet strategy +#### Bet strategy - **MOST_VOTED**: Select the option most voted based on users count - **HIGH_ODDS**: Select the option with the highest odds @@ -294,6 +295,21 @@ Here a concrete example: - **PERCENTAGE**: The highest percentage is 56% for **'under 7.5'** - **SMART**: Calculate the percentage based on the users. The percentage are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percatage are highter thant `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 | Value allowed | Default | Description | +|------------- |------------- |-------------------------------------------------------------------------------- |--------- |---------------------------------------------------------------------------------- | +| `key` | OutcomeKeys | PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS | None | Key to apply the filter | +| `condition` | Condition | GT, LT, GTE, LTE | None | Condition that should match for place bet | +| `value` | number | | None | Value to compare | +| `decision` | bool | True, False | None | If True the filter apply base on decision of the bet. If False calculate the sum | + +#### Example +- If you want to place the bet ONLY if the total of users participants in the bet are greater than 200 +`FilterCondition(key=OutcomeKeys.TOTAL_USERS, condition=Condition.GT, value=200, decision=False)` +- If you want to place the bet ONLY if the winning odd of your decision is greater than or equal 1.3 +`FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.GTE, value=1.3, decision=True)` +- If you want to place the bet ONLY if the sum of highest bet is lower than 2000 +`FilterCondition(key=OutcomeKeys.TOP_POINTS, condition=Condition.LT, value=200, decision=2000)` ## Migrating from old repository (the original one): If you already have a `twitch-cookies.pkl` and you don't want to login again please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 655a99f..745aab4 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -70,7 +70,7 @@ class BetSettings(object): def default(self): self.strategy = self.strategy if not None else Strategy.SMART self.percentage = self.percentage if not None else 5 - self.percentage_gap = self.percentage_gap if not None else 2 + self.percentage_gap = self.percentage_gap if not None else 20 self.max_points = self.max_points if not None else 50000 self.stealth_mode = self.stealth_mode if not None else False From 0751175acfc43df2a18c517a72d5a34967885d61 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 17:24:40 +0100 Subject: [PATCH 025/124] Remove allowed values from tables. Make issue linkable --- README.md | 79 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 93733ac..c4db4d8 100644 --- a/README.md +++ b/README.md @@ -243,41 +243,41 @@ If the browser are currently betting or wait for more data It's impossible to in ## Settings ### LoggerSettings -| Key | Type | Value allowed | Default | Description | -|----------------- |----------------- |--------------- |-------------------------------- |-------------------------------------------------------------------------------------- | -| `save` | bool | True, False | True | If you want to save logs in file (suggested) | -| `less` | bool | True, False | False | Reduce the logging format and message verbosity #10 | -| `console_level` | int / logging.* | | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `file_level` | int / logging.* | | logging.DEBUG | Level of logs in file save - If you think the log file it's too big use logging.INFO | -| `emoji` | bool | True, False | For Windows is False else True | On Windows we have a problem to print emoji. Set to false if you have a problem | +| 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. | +| `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 to print emoji. Set to false if you have a problem | ### BrowserSettings -| Key | Type | Value allowed | Default | Description | -|------------------- |--------- |----------------- |--------- |------------------------------------------------------------------------------------------------- | -| `timeout` | float | Positive | 10 | If no element was found by Selenium raise exception after timeouts. Increase on slow connection | -| `implicitly_wait` | int | Positive | 5 | Wait x seconds after continue Selenium execution | -| `max_attempts` | int | Positive | 3 | Number of max attempt for place bet | -| `do_screenshot` | bool | True, False | False | Save screenshot before/after do some Selenium action - Help debug | -| `save_html` | bool | True, False | False | Save html content before/after do some Selenium action - Help debug | -| `show` | bool | True, False | True | Choose if you want to see or not the browser - Help debug | -| `browser` | Browser | CHROME, FIREFOX | FIREFOX | Choose your favourite browser | -| `driver_path` | str | /path/ | None | Write the path of chromedriver or geckodriver | +| Key | Type | Default | Description | +|------------------- |--------- |--------- |------------------------------------------------------------------------------------------------- | +| `timeout` | float | 10 | If no element was found by Selenium raise exception after timeouts. Increase on slow connection | +| `implicitly_wait` | int | 5 | Wait x seconds after continue Selenium execution | +| `max_attempts` | int | 3 | Number of max attempt for place bet | +| `do_screenshot` | bool | False | Save screenshot before/after do some Selenium action - Help debug | +| `save_html` | bool | False | Save html content before/after do some Selenium action - Help debug | +| `show` | bool | True | Choose if you want to see or not the browser - Help debug | +| `browser` | Browser | FIREFOX | Choose your favourite browser | +| `driver_path` | str | None | Write the path of chromedriver or geckodriver | ### StreamerSettings -| Key | Type | Value allowed | Default | Description | -|-------------------- |------------- |--------------- |-------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `make_predictions` | bool | True, False | True | Choose if you want to make predictions / bet or not | -| `follow_raid` | bool | True, False | True | Choose if you want to follow raid +250 points | -| `claim_drops` | bool | True, False | True | If this values is True the script will increase the watch-time for the current game. With this you are able to claim the drops from Twitch Inventory #21 | -| `watch_streak` | bool | True, False | True | Choose if you want to change priority for this streamers and try to catch the Watch Streak event #11 | -| `bet` | BetSettings | | Default values for BetSettings | Rules to follow for the bet | +| 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 values is True the script will increase the watch-time for the current game. With this you are able to claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `watch_streak` | bool | True | Choose if you want to change priority for this streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `bet` | BetSettings | Default values for BetSettings | Rules to follow for the bet | ### BetSettings -| Key | Type | Value allowed | Default | Description | -|-------------------- |----------------- |------------------------------------------ |--------- |---------------------------------------------------------------------------------------------------------------- | -| `strategy` | Strategy | MOST_VOTED, HIGH_ODDS, PERCENTAGE, SMART | SMART | Choose your strategy! See above for more info | -| `percentage` | int | Positive | 5 | Place the x% of your channel points | -| `percentage_gap` | int | Positive | 20 | Gap difference between outcomesA and outcomesB (for SMART stragegy) | -| `max_points` | int | Positive | 50000 | If the x percentage of your channel points is GT bet_max_points set this value | -| `stealth_mode` | bool | True, False | False | If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 | -| `filter_condition` | FilterCondition | | None | Based on this filter the script will skip some bet #29 | +| Key | Type | Default | Description | +|-------------------- |----------------- |--------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `strategy` | Strategy | SMART | Choose your strategy! See above 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) | +| `filter_condition` | FilterCondition | None | Based on this filter the script will skip some bet [#29](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/29) | #### Bet strategy @@ -296,12 +296,15 @@ Here a concrete example: - **SMART**: Calculate the percentage based on the users. The percentage are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percatage are highter thant `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 | Value allowed | Default | Description | -|------------- |------------- |-------------------------------------------------------------------------------- |--------- |---------------------------------------------------------------------------------- | -| `key` | OutcomeKeys | PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS | None | Key to apply the filter | -| `condition` | Condition | GT, LT, GTE, LTE | None | Condition that should match for place bet | -| `value` | number | | None | Value to compare | -| `decision` | bool | True, False | None | If True the filter apply base on decision of the bet. If False calculate the sum | +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `key` | OutcomeKeys | None | Key to apply the filter | +| `condition` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | +| `decision` | bool | None | If True the filter apply base on decision of the bet. If False calculate the sum | + +- Allowed values for `key` are: `PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS` +- Allowed values for `condition` are: `GT, LT, GTE, LTE` #### Example - If you want to place the bet ONLY if the total of users participants in the bet are greater than 200 From bcd252a07af7c82d519c11d26465420cf87915fa Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 17:28:20 +0100 Subject: [PATCH 026/124] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c4db4d8..9e64733 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ If the browser are currently betting or wait for more data It's impossible to in | `follow_raid` | bool | True | Choose if you want to follow raid +250 points | | `claim_drops` | bool | True | If this values is True the script will increase the watch-time for the current game. With this you are able to claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | | `watch_streak` | bool | True | Choose if you want to change priority for this streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | -| `bet` | BetSettings | Default values for BetSettings | Rules to follow for the bet | +| `bet` | BetSettings | | Rules to follow for the bet | ### BetSettings | Key | Type | Default | Description | |-------------------- |----------------- |--------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -303,8 +303,10 @@ In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot wi | `value` | number | None | Value to compare | | `decision` | bool | None | If True the filter apply base on decision of the bet. If False calculate the sum | -- Allowed values for `key` are: `PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS` -- Allowed values for `condition` are: `GT, LT, GTE, LTE` +- Allowed values for `key` are: +`PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS` +- Allowed values for `condition` are: +`GT, LT, GTE, LTE` #### Example - If you want to place the bet ONLY if the total of users participants in the bet are greater than 200 From c92e72af00a1405e5c137c833658100ae5560df2 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 17:50:30 +0100 Subject: [PATCH 027/124] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e64733..c839438 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot wi - If you want to place the bet ONLY if the winning odd of your decision is greater than or equal 1.3 `FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.GTE, value=1.3, decision=True)` - If you want to place the bet ONLY if the sum of highest bet is lower than 2000 -`FilterCondition(key=OutcomeKeys.TOP_POINTS, condition=Condition.LT, value=200, decision=2000)` +`FilterCondition(key=OutcomeKeys.TOP_POINTS, condition=Condition.LT, value=2000, decision=False)` ## Migrating from old repository (the original one): If you already have a `twitch-cookies.pkl` and you don't want to login again please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` From 80f8ebd4766fb5464a13ec7ce1eff1f1c8f0c0ea Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Wed, 3 Feb 2021 18:35:09 +0100 Subject: [PATCH 028/124] Attempt to fix PREDICTION showing with count -1 in final report --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 4 +--- TwitchChannelPointsMiner/classes/entities/Streamer.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 8c14077..d860865 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -293,9 +293,8 @@ class WebSocketsPool: "REFUND", -points_placed, counter=-1, - create_if_missing=False, ) - else: + elif result_type == "WIN": logger.info( "REMOVE PREDICTION FROM POINTS MSG" ) # TODO remove @@ -303,7 +302,6 @@ class WebSocketsPool: "PREDICTION", -points_won, counter=-1, - create_if_missing=False, ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 301aa57..a97b774 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -91,10 +91,8 @@ class Streamer(object): ] ) - def update_history(self, reason_code, earned, counter=1, create_if_missing=True): + def update_history(self, reason_code, earned, counter=1): if reason_code not in self.history: - if not create_if_missing: - return self.history[reason_code] = {"counter": 0, "amount": 0} self.history[reason_code]["counter"] += counter self.history[reason_code]["amount"] += earned From 5f61ce10f0c53c188137106352ac0393c567951f Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 3 Feb 2021 22:37:37 +0100 Subject: [PATCH 029/124] Update variable name according to discussion https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/commit/f7b74bffa8e302163ad698a2e449eba1939a4292#r46705304 --- README.md | 35 +++++++++++-------- .../classes/entities/Bet.py | 18 +++++----- example.py | 7 ++-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c839438..98503d9 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,9 @@ twitch_miner = TwitchChannelPointsMiner( percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value filter_condition=FilterCondition( - key=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] - condition=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value - value=800, # - decision=False # If the filter should apply to the decision or on the sum + by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + where=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value + value=800 ) ) ) @@ -298,23 +297,29 @@ In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot wi ### FilterCondition | Key | Type | Default | Description | |------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `key` | OutcomeKeys | None | Key to apply the filter | -| `condition` | Condition | None | Condition that should match for place bet | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | | `value` | number | None | Value to compare | -| `decision` | bool | None | If True the filter apply base on decision of the bet. If False calculate the sum | -- Allowed values for `key` are: -`PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS` -- Allowed values for `condition` are: -`GT, LT, GTE, LTE` +Allowed values for `key` 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 points) +- `TOP_POINTS` (no sum) [Doesn't make sense to the top points of both sides] +- `TOTAL_USERS` (sum) +- `TOTAL_POINTS` (sum) + +Allowed values for `condition` are: `GT, LT, GTE, LTE` #### Example - If you want to place the bet ONLY if the total of users participants in the bet are greater than 200 -`FilterCondition(key=OutcomeKeys.TOTAL_USERS, condition=Condition.GT, value=200, decision=False)` +`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 1.3 -`FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.GTE, value=1.3, decision=True)` -- If you want to place the bet ONLY if the sum of highest bet is lower than 2000 -`FilterCondition(key=OutcomeKeys.TOP_POINTS, condition=Condition.LT, value=2000, decision=False)` +`FilterCondition(by=OutcomeKeys.ODDS, where=Condition.GTE, value=1.3)` +- If you want to place the bet ONLY if highest bet is lower than 2000 +`FilterCondition(by=OutcomeKeys.TOP_POINTS, where=Condition.LT, value=2000)` ## Migrating from old repository (the original one): If you already have a `twitch-cookies.pkl` and you don't want to login again please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 745aab4..877028a 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -40,14 +40,13 @@ class OutcomeKeys(object): class FilterCondition(object): - def __init__(self, key=None, condition=None, value=None, decision=None): - self.key = key - self.condition = condition + def __init__(self, by=None, where=None, value=None, decision=None): + self.by = by + self.where = where self.value = value - self.decision = decision def __repr__(self): - return f"FilterCondition(Key={self.key}, Condition={self.condition}, Value={self.value}, Decision={self.decision})" + return f"FilterCondition(By={self.key}, Where={self.condition}, Value={self.value})" class BetSettings(object): @@ -171,15 +170,16 @@ class Bet(object): def skip(self) -> bool: if self.settings.filter_condition is not None: - key = self.settings.filter_condition.key - condition = self.settings.filter_condition.condition + # key == by , condition == where + key = self.settings.filter_condition.by + condition = self.settings.filter_condition.where value = self.settings.filter_condition.value - compared_value = ( (self.outcomes[0][key] + self.outcomes[1][key]) - if self.settings.filter_condition.decision is False + if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS] else self.outcomes[char_decision_as_index(self.decision["choice"])][key] ) + logger.info( f"Filter applied on this bet. Current {key} is {compared_value}, must be {condition} {value}" ) diff --git a/example.py b/example.py index ab11ead..e3077dd 100644 --- a/example.py +++ b/example.py @@ -33,10 +33,9 @@ twitch_miner = TwitchChannelPointsMiner( percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value filter_condition=FilterCondition( - key=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] - condition=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value - value=800, # - decision=False # If the filter should apply to the decision or on the sum + by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + where=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value + value=800 ) ) ) From c6a2f4ec6296dbe1328719f5baf718995abd7181 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 00:31:38 +0100 Subject: [PATCH 030/124] Update example.py and README.md with stealth_mode --- README.md | 15 ++++++++------- example.py | 11 ++++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 98503d9..3107d8e 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,10 @@ twitch_miner = TwitchChannelPointsMiner( percentage=5, # Place the x% of your channel points percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) 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 #33 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, # The key must be [GT, LT, GTE, LTE] than value + by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] + where=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value value=800 ) ) @@ -207,11 +208,11 @@ twitch_miner = TwitchChannelPointsMiner( 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 , percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), - Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , percentage_gap=20 , max_points=1234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), - Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , percentage_gap=30 , max_points=50000 , filter_condition=FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), - 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 , percentage_gap=20 , max_points=90 , filter_condition=FilterCondition(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), + 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(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), + 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(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), + 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(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), + 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(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), Streamer("streamer-username06"), Streamer("streamer-username07"), Streamer("streamer-username08"), diff --git a/example.py b/example.py index e3077dd..f56cd0c 100644 --- a/example.py +++ b/example.py @@ -32,6 +32,7 @@ twitch_miner = TwitchChannelPointsMiner( percentage=5, # Place the x% of your channel points percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) 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 #33 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, # The key must be [GT, LT, GTE, LTE] than value @@ -51,11 +52,11 @@ twitch_miner = TwitchChannelPointsMiner( 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 , percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), - Streamer("streamer-username02", settings=StreamerSettings(make_predictions=False , follow_raid=True , claim_drops=False , bet=BetSettings(strategy=Strategy.PERCENTAGE , percentage=5 , percentage_gap=20 , max_points=1234 , filter_condition=FilterCondition(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), - Streamer("streamer-username03", settings=StreamerSettings(make_predictions=True , follow_raid=False , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , percentage_gap=30 , max_points=50000 , filter_condition=FilterCondition(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), - 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 , percentage_gap=20 , max_points=90 , filter_condition=FilterCondition(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), + 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(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), + 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(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), + 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(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), + 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(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), Streamer("streamer-username06"), Streamer("streamer-username07"), Streamer("streamer-username08"), From 4ef5baddac573d29836cd7555fa4db014dc84826 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 00:55:34 +0100 Subject: [PATCH 031/124] After the first test i lose the ability to ping twitch server and I'm able to reconnect. I think that an error raised (now logged) and ws.keep_running was set to False by lib. So remove handling of keep_running. Log close and error so let's wee see --- .../TwitchChannelPointsMiner.py | 12 ++--- .../classes/TwitchWebSocket.py | 1 - .../classes/WebSocketsPool.py | 45 ++++++++++--------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 58ec957..d7b16e1 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -248,14 +248,16 @@ class TwitchChannelPointsMiner: 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].elapsed_last_ping() > 5: + if ( + self.ws_pool.ws[index] is not None + and self.ws_pool.ws[index].elapsed_last_ping() > 10 + ): logger.info( - f"#{index} - The last ping was sent more than 5 minutes ago. Reconnecting to the WebSocket..." - ) - WebSocketsPool.handle_websocket_reconnection( - self.ws_pool.ws[index] + f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..." ) + WebSocketsPool.handle_reconnection(self.ws_pool.ws[index]) def end(self, signum, frame): logger.info("CTRL+C Detected! Please wait just a moments!") diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index 9d99242..26b75a4 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -33,7 +33,6 @@ class TwitchWebSocket(WebSocketApp): def reset(self, parent_pool): self.parent_pool = parent_pool - self.keep_running = True self.is_closed = False self.is_opened = False self.is_reconneting = False diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 925e772..01b6a80 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -55,7 +55,9 @@ class WebSocketsPool: url=WEBSOCKET, on_message=WebSocketsPool.on_message, on_open=WebSocketsPool.on_open, - on_close=WebSocketsPool.handle_websocket_reconnection, + on_error=WebSocketsPool.on_error, + on_close=WebSocketsPool.on_close + # on_close=WebSocketsPool.handle_reconnection, # Do nothing. ) ) self.ws[-1].reset(self) @@ -66,7 +68,6 @@ class WebSocketsPool: def end(self): for index in range(0, len(self.ws)): - self.ws[index].keep_running = False self.ws[index].close() @staticmethod @@ -81,31 +82,35 @@ class WebSocketsPool: ws.ping() time.sleep(random.uniform(25, 30)) - if ws.elapsed_last_pong() > 15 and ws.is_reconneting is False: + if ws.elapsed_last_pong() > 10 and ws.is_reconneting is False: logger.info( - f"#{ws.index} - The last pong was received more than 15 minutes ago. Reconnect the WebSocket" + f"#{ws.index} - The last PONG was received more than 10 minutes ago. Reconnect the WebSocket" ) - ws.keep_running = True ws.is_reconneting = True - WebSocketsPool.handle_websocket_reconnection(ws) + WebSocketsPool.handle_reconnection(ws) thread_ws = threading.Thread(target=run) thread_ws.daemon = True thread_ws.start() @staticmethod - def handle_websocket_reconnection(ws): - ws.is_closed = True - if ws.keep_running is True: - logger.info( - f"#{ws.index} - Reconnecting to Twitch PubSub server in 60 seconds" - ) - time.sleep(60) + def on_error(ws, error): + logger.error(f"#{ws.index} - WebSocket error: {error}") - self = ws.parent_pool - self.ws[ws.index] = None - for topic in ws.topics: - self.submit(topic) + @staticmethod + def on_close(ws): + logger.info(f"#{ws.index} - WebSocket closed") + + @staticmethod + def handle_reconnection(ws): + ws.is_closed = True + logger.info(f"#{ws.index} - Reconnecting to Twitch PubSub server in 30 seconds") + time.sleep(30) + + self = ws.parent_pool + self.ws[ws.index] = None + for topic in ws.topics: + self.submit(topic) @staticmethod def on_message(ws, message): @@ -312,11 +317,9 @@ class WebSocketsPool: raise RuntimeError(f"Error while trying to listen for a topic: {response}") elif response["type"] == "RECONNECT": - logger.info( - f"#{ws.index} - Reconnection required and keep running is: {ws.keep_running}" - ) + logger.info(f"#{ws.index} - Reconnection required") ws.is_reconneting = True - WebSocketsPool.handle_websocket_reconnection(ws) + WebSocketsPool.handle_reconnection(ws) elif response["type"] == "PONG": ws.last_pong = time.time() From d84a45bb0d7aa62587674fc64d228a516f68c4cd Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 11:03:51 +0100 Subject: [PATCH 032/124] Reconnection automatically after ws close. def end() check if ws is None else forced_close. Move reset method inside __init__ --- .../classes/TwitchWebSocket.py | 49 ++++++++++--------- .../classes/WebSocketsPool.py | 27 +++++++--- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index 26b75a4..9c81973 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -10,10 +10,36 @@ logger = logging.getLogger(__name__) class TwitchWebSocket(WebSocketApp): - def __init__(self, index, *args, **kw): + 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_reconneting = False + self.forced_close = False + + # Custom attribute + self.topics = [] + self.pending_topics = [] + + self.twitch = parent_pool.twitch + self.browser = parent_pool.browser + 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: @@ -31,27 +57,6 @@ class TwitchWebSocket(WebSocketApp): logger.debug(f"#{self.index} - Send: {request_str}") super().send(request_str) - def reset(self, parent_pool): - self.parent_pool = parent_pool - self.is_closed = False - self.is_opened = False - self.is_reconneting = False - - # Custom attribute - self.topics = [] - self.pending_topics = [] - - self.twitch = parent_pool.twitch - self.browser = parent_pool.browser - 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 elapsed_last_pong(self): return (time.time() - self.last_pong) // 60 diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 01b6a80..95647ec 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -52,6 +52,7 @@ class WebSocketsPool: self.ws.append( TwitchWebSocket( index=len(self.ws), + parent_pool=self, url=WEBSOCKET, on_message=WebSocketsPool.on_message, on_open=WebSocketsPool.on_open, @@ -60,7 +61,6 @@ class WebSocketsPool: # on_close=WebSocketsPool.handle_reconnection, # Do nothing. ) ) - self.ws[-1].reset(self) self.thread_ws = threading.Thread(target=lambda: self.ws[-1].run_forever()) self.thread_ws.daemon = True @@ -68,7 +68,9 @@ class WebSocketsPool: def end(self): for index in range(0, len(self.ws)): - self.ws[index].close() + if self.ws[index] is not None: + self.ws[index].forced_close = True + self.ws[index].close() @staticmethod def on_open(ws): @@ -100,17 +102,26 @@ class WebSocketsPool: @staticmethod def on_close(ws): logger.info(f"#{ws.index} - WebSocket closed") + # On close please reconnect automatically + WebSocketsPool.handle_reconnection(ws) @staticmethod def handle_reconnection(ws): + # Close the current WebSocket. + # anyway, we replace the ws with None ws.is_closed = True - logger.info(f"#{ws.index} - Reconnecting to Twitch PubSub server in 30 seconds") - time.sleep(30) + ws.keep_running = False + # Reconnect only if ws.forced_close is False (replace the keep_running) + if ws.forced_close is False: + logger.info( + f"#{ws.index} - Reconnecting to Twitch PubSub server in 30 seconds" + ) + time.sleep(30) - self = ws.parent_pool - self.ws[ws.index] = None - for topic in ws.topics: - self.submit(topic) + self = ws.parent_pool + self.ws[ws.index] = None + for topic in ws.topics: + self.submit(topic) @staticmethod def on_message(ws, message): From dc0d9874e548089e973ab50c385536a450fe7169 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 11:34:00 +0100 Subject: [PATCH 033/124] :nauseated_face: Just for start the test, I'll refactory later --- TwitchChannelPointsMiner/classes/TwitchBrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py index 0960142..05acd8b 100644 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ b/TwitchChannelPointsMiner/classes/TwitchBrowser.py @@ -16,7 +16,7 @@ from selenium.webdriver.support.ui import WebDriverWait from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction from TwitchChannelPointsMiner.constants.browser import Javascript, Selectors from TwitchChannelPointsMiner.constants.twitch import URL -from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_user_agent +from TwitchChannelPointsMiner.utils import _millify, get_user_agent logger = logging.getLogger(__name__) @@ -265,7 +265,7 @@ class TwitchBrowser: def start_bet(self, event: EventPrediction): start_time = time.time() - if bet_condition(self, event, logger) is True: + if 1 == 1: # bet_condition(self, event, logger) is True: for attempt in range(0, self.settings.max_attempts): logger.info( f"Starting betting for {event} owned by {event.streamer}", From 0cce504ef21c58082e75c0d1c702a3505e2aac18 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 14:51:31 +0100 Subject: [PATCH 034/124] Log message as described in #45 comment. Print decision/points/event/owner --- TwitchChannelPointsMiner/classes/Twitch.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index b6b7200..37b4c32 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -23,6 +23,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import ( from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin from TwitchChannelPointsMiner.constants.twitch import API, CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.utils import _millify logger = logging.getLogger(__name__) @@ -199,6 +200,17 @@ class Twitch: 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} owned by {event.streamer}", + extra={"emoji": ":four_leaf_clover:"}, + ) + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) + json_data = copy.deepcopy(GQLOperations.MakePrediction) json_data["variables"] = { "input": { From d71f37ad58dd1e0b9c400b19ff23f48db2c61f01 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 4 Feb 2021 23:52:29 +0100 Subject: [PATCH 035/124] oh damn --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 95647ec..0db2d09 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -38,7 +38,7 @@ class WebSocketsPool: """ def submit(self, topic): - if self.ws == [] or len(self.ws[-1].topics) >= 50: + if self.ws == [] or self.ws[-1] is None or len(self.ws[-1].topics) >= 50: self.append_new_websocket() self.ws[-1].topics.append(topic) From 788b0f25b734bf7efcaffe478c8298c59fdb0805 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 5 Feb 2021 00:11:31 +0100 Subject: [PATCH 036/124] Remove any reference to Browser-Selenium --- .github/ISSUE_TEMPLATE/bug_report.md | 5 - README.md | 22 - .../TwitchChannelPointsMiner.py | 8 +- .../classes/TwitchBrowser.py | 453 ------------------ TwitchChannelPointsMiner/utils.py | 6 +- example.py | 6 - requirements.txt | 1 - 7 files changed, 4 insertions(+), 497 deletions(-) delete mode 100644 TwitchChannelPointsMiner/classes/TwitchBrowser.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e115913..80b236b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,13 +20,8 @@ Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. -You can enable the screenshot using: `BrowserSettings(do_screenshot=True)` - **Desktop (please complete the following information):** - OS: [e.g. Windows] - - Browser [e.g. chrome, firefox] - Python version [e.g. 3.x] **Additional context** diff --git a/README.md b/README.md index 714777a..9a39093 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,6 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings -from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", @@ -173,11 +172,6 @@ twitch_miner = TwitchChannelPointsMiner( emoji=True, # On Windows we have a problem to print emoji. Set to false if you have a problem less=False # If you think that the logs are too much verborse set this to True ), - browser_settings=BrowserSettings( - browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser - show=False, # Show the browser during bet else headless mode - do_screenshot=False, # Do screenshot during the bet - ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction follow_raid=True, # Follow raid to obtain more points @@ -232,8 +226,6 @@ twitch_miner.mine(["streamer1", "streamer2"], followers=True) # Mixed Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` Twitch return the streamers order by followed_at. So your last follow have the highest priority. -If the browser are currently betting or wait for more data It's impossible to interact with another event prediction from another streamer. - ### Bet strategy - **MOST_VOTED**: Select the option most voted based on users count @@ -268,26 +260,12 @@ Other users have find multiple problems on Windows my suggestion are: Other usefully infos can be founded here: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner/issues/31 -## Use Chrome instead Firefox -If you prefer Chrome instead Firefox please download the WebDriver matching with your Chrome version and OS from this link: https://chromedriver.chromium.org/downloads. -Extract the archivie, copy the chromedriver file in this project folder. -Edit your run.py file and the browser_settings should something like this: -```python -browser_settings=BrowserSettings( - browser=Browser.CHROME, - driver_path="/path/of/your/chromedriver" # If no path was provided the script will try to search automatically -), -``` - ## Issue / Debug When you open a new issue please use the correct template. Please provide at least the following information/files: -- Browser (if you have the prediction feature enabled) - Operation System - Python Version - logs/ `LoggerSettings(file_level=logging.DEBUG)` -- htmls/ `BrowserSettings(save_html=True)` -- screenshots/ `BrowserSettings(do_screenshot=True)` Make sure also to have the latest commit. diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index fdc32b7..5c9795c 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -19,7 +19,6 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import ( from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Twitch import Twitch -from TwitchChannelPointsMiner.classes.TwitchBrowser import BrowserSettings from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers from TwitchChannelPointsMiner.utils import ( @@ -45,7 +44,6 @@ class TwitchChannelPointsMiner: # Settings for logging and selenium as you can see. # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), - browser_settings: BrowserSettings = BrowserSettings(), # Default values for all streamers streamer_settings: StreamerSettings = StreamerSettings(), ): @@ -53,17 +51,15 @@ class TwitchChannelPointsMiner: # Set as globally config Settings.logger = logger_settings - Settings.browser = browser_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(browser_settings.browser) + user_agent = get_user_agent("FIREFOX") self.twitch = Twitch(self.username, user_agent) - self.twitch_browser = None self.claim_drops_startup = claim_drops_startup self.streamers = [] self.events_predictions = {} @@ -251,7 +247,7 @@ class TwitchChannelPointsMiner: self.minute_watcher_thread.join() self.__print_report() - time.sleep(3.5) # Do sleep for ending browser and threads + time.sleep(3.5) # Do sleep for ending threads ... sys.exit(0) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py deleted file mode 100644 index 05acd8b..0000000 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ /dev/null @@ -1,453 +0,0 @@ -import logging -import os -import platform -import random -import time -from enum import Enum, auto -from pathlib import Path - -from selenium import webdriver -from selenium.common.exceptions import JavascriptException, TimeoutException -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions -from selenium.webdriver.support.ui import WebDriverWait - -from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction -from TwitchChannelPointsMiner.constants.browser import Javascript, Selectors -from TwitchChannelPointsMiner.constants.twitch import URL -from TwitchChannelPointsMiner.utils import _millify, get_user_agent - -logger = logging.getLogger(__name__) - - -class Browser(Enum): - CHROME = auto() - FIREFOX = auto() - - -class BrowserSettings: - def __init__( - self, - timeout: float = 10.0, - implicitly_wait: int = 5, - max_attempts: int = 3, - do_screenshot: bool = False, # Options for debug - save_html: bool = False, # Options for debug - show: bool = True, - browser: Browser = Browser.FIREFOX, - driver_path: str = None, - ): - self.timeout = timeout - self.implicitly_wait = implicitly_wait - self.max_attempts = max_attempts - self.do_screenshot = do_screenshot - self.save_html = save_html - self.show = show - self.browser = browser - self.driver_path = ( - driver_path - if driver_path is not None - else os.path.join( - Path().absolute(), - ( - ("chromedriver" if browser == Browser.CHROME else "geckodriver") - + (".exe" if platform.system() == "Windows" else "") - ), - ) - ) - - -class TwitchBrowser: - def __init__( - self, - auth_token: str, - session_id: str, - settings: BrowserSettings, - ): - self.auth_token = auth_token - self.session_id = session_id - self.settings = settings - - self.currently_is_betting = False - self.browser = None - - def init(self): - if self.settings.browser == Browser.FIREFOX: - self.__init_firefox() - elif self.settings.browser == Browser.CHROME: - self.__init_chrome() - - if self.browser is not None: - self.browser.set_window_size(450, 800) - self.browser.implicitly_wait(self.settings.implicitly_wait) - - self.__init_twitch() - - def __init_twitch(self): - logger.debug( - "Init Twitch page - Cookies - LocalStorage items", - extra={"emoji": ":wrench:"}, - ) - cookie = { - "domain": ".twitch.tv", - "hostOnly": False, - "httpOnly": False, - "name": "auth-token", - "path": "/", - "SameSite": "no_restriction", - "secure": True, - "session": False, - "storeId": "0", - "id": 1, - "value": self.auth_token, - } - self.browser.get(URL) - self.browser.add_cookie(cookie) - time.sleep(random.uniform(2.5, 3.5)) - - self.__click_when_exist( - Selectors.cookiePolicy, By.CSS_SELECTOR, suppress_error=True - ) - time.sleep(random.uniform(0.5, 1.5)) - - # Edit value in localStorage for dark theme, point consent etc. - self.__execute_script(Javascript.localStorage) - time.sleep(random.uniform(0.5, 1.5)) - self.__blank() - - def __blank(self): - self.browser.get("about:blank") - - def __execute_script(self, javascript_code, suppress_error=False): - try: - self.browser.execute_script(javascript_code) - return True - except JavascriptException: - if suppress_error is False: - logger.warning(f"Failed to execute: {javascript_code}") - return False - - # Private method __ - We can instantiate webdriver only with init_browser - def __init_chrome(self): - logger.debug("Init Chrome browser", extra={"emoji": ":wrench:"}) - options = webdriver.ChromeOptions() - if not self.settings.show: - options.add_argument("headless") - - options.add_argument("mute-audio") - options.add_argument("disable-dev-shm-usage") - options.add_argument("disable-accelerated-2d-canvas") - options.add_argument("no-first-run") - options.add_argument("no-zygote") - options.add_argument("disable-gpu") - options.add_argument("no-sandbox") - options.add_argument("disable-setuid-sandbox") - options.add_argument("disable-infobars") - options.add_argument(f"user-agent={get_user_agent(self.settings.browser)}") - - options.add_experimental_option( - "prefs", {"profile.managed_default_content_settings.images": 2} - ) - options.add_experimental_option("useAutomationExtension", False) - options.add_experimental_option( - "excludeSwitches", ["enable-automation", "enable-logging"] - ) - - if os.path.isfile(self.settings.driver_path) is True: - self.browser = webdriver.Chrome(self.settings.driver_path, options=options) - else: - logger.warning( - f"The path {self.settings.driver_path} is not valid. Using default path...", - extra={"emoji": ":wrench:"}, - ) - self.browser = webdriver.Chrome(options=options) - - # Private method __ - We can instantiate webdriver only with init_browser - def __init_firefox(self): - logger.debug("Init Firefox browser", extra={"emoji": ":wrench:"}) - options = webdriver.FirefoxOptions() - if not self.settings.show: - options.headless = True - - fp = webdriver.FirefoxProfile() - fp.set_preference("permissions.default.image", 2) - fp.set_preference("permissions.default.stylesheet", 2) - fp.set_preference("dom.ipc.plugins.enabled.libflashplayer.so", "false") - fp.set_preference("media.volume_scale", "0.0") - fp.set_preference("browser.startup.homepage", "about:blank") - fp.set_preference("startup.homepage_welcome_url", "about:blank") - fp.set_preference("startup.homepage_welcome_url.additional", "about:blank") - fp.set_preference( - "general.useragent.override", - get_user_agent(self.settings.browser), - ) - - if os.path.isfile(self.settings.driver_path) is True: - self.browser = webdriver.Firefox( - executable_path=self.settings.driver_path, - options=options, - firefox_profile=fp, - ) - else: - logger.warning( - f"The path {self.settings.driver_path} is not valid. Using default path...", - extra={"emoji": ":wrench:"}, - ) - self.browser = webdriver.Firefox(options=options, firefox_profile=fp) - - def __debug(self, event, method): - if self.settings.do_screenshot: - self.screenshot(f"{event.event_id}___{method}") - if self.settings.save_html: - self.save_html(f"{event.event_id}___{method}") - - def save_html(self, fname): - htmls_path = os.path.join(Path().absolute(), "htmls") - Path(htmls_path).mkdir(parents=True, exist_ok=True) - Path(os.path.join(htmls_path, self.session_id)).mkdir( - parents=True, exist_ok=True - ) - - fname = f"{fname}.html" if fname.endswith(".html") is False else fname - fname = fname.replace(".html", f".{time.time()}.html") - fname = os.path.join(htmls_path, self.session_id, fname) - - # Little delay ... - time.sleep(0.2) - with open(fname, "w", encoding="utf-8") as writer: - writer.write(self.browser.page_source) - - def screenshot(self, fname): - screenshots_path = os.path.join(Path().absolute(), "screenshots") - Path(screenshots_path).mkdir(parents=True, exist_ok=True) - Path(os.path.join(screenshots_path, self.session_id)).mkdir( - parents=True, exist_ok=True - ) - - fname = f"{fname}.png" if fname.endswith(".png") is False else fname - fname = fname.replace(".png", f".{time.time()}.png") - fname = os.path.join(screenshots_path, self.session_id, fname) - # Little pause prevent effect/css animations in browser delayed - time.sleep(0.1) - self.browser.save_screenshot(fname) - - def __click_when_exist( - self, selector, by: By = By.CSS_SELECTOR, suppress_error=False, timeout=None - ) -> bool: - timeout = self.settings.timeout if timeout is None else timeout - try: - element = WebDriverWait(self.browser, timeout).until( - expected_conditions.element_to_be_clickable((by, selector)) - ) - ActionChains(self.browser).move_to_element(element).click().perform() - return True - except Exception: - if suppress_error is False: - logger.error(f"Exception raised with: {selector}", exc_info=True) - return False - - def __send_text( - self, selector, text, by: By = By.CSS_SELECTOR, suppress_error=False - ) -> bool: - try: - element = WebDriverWait(self.browser, self.settings.timeout).until( - expected_conditions.element_to_be_clickable((by, selector)) - ) - ActionChains(self.browser).move_to_element(element).click().send_keys( - text - ).perform() - return True - except Exception: - if suppress_error is False: - logger.error(f"Exception raised with: {selector}", exc_info=True) - return False - - def start_bet(self, event: EventPrediction): - start_time = time.time() - if 1 == 1: # bet_condition(self, event, logger) is True: - for attempt in range(0, self.settings.max_attempts): - logger.info( - f"Starting betting for {event} owned by {event.streamer}", - extra={"emoji": ":wrench:"}, - ) - self.browser.get(event.streamer.chat_url) - time.sleep(random.uniform(3, 5)) - self.__click_when_exist( - Selectors.cookiePolicy, - By.CSS_SELECTOR, - suppress_error=True, - timeout=1.5, - ) - - # Hide the chat ... Don't ask me why - self.__execute_script(Javascript.clearStyleChat, suppress_error=True) - - if self.__bet_chains_methods(event) is True: - return self.currently_is_betting, time.time() - start_time - logger.error( - f"Attempt {attempt+1} failed!", extra={"emoji": ":wrench:"} - ) - self.__blank() # If we fail return to blank page - return False, time.time() - start_time - - def __bet_chains_methods(self, event) -> bool: - if self.__open_coins_menu(event) is True: - if self.__click_on_bet(event) is True: - if self.__enable_custom_bet_value(event) is True: - return True - return False - - def place_bet(self, event: EventPrediction): - logger.info( - f"Going to complete bet for {event} owned by {event.streamer}", - extra={"emoji": ":wrench:"}, - ) - if event.status == "ACTIVE": - if event.box_fillable and self.currently_is_betting: - - div_bet_is_open = False - self.__debug(event, "place_bet") - try: - WebDriverWait(self.browser, 1).until( - expected_conditions.visibility_of_element_located( - (By.XPATH, Selectors.betMainDivXP) - ) - ) - div_bet_is_open = True - except TimeoutException: - logger.info( - "The bet div was not found, maybe It was closed. Attempting to open again... Hopefully in time!", - extra={"emoji": ":wrench:"}, - ) - div_bet_is_open = self.__bet_chains_methods(event) - if div_bet_is_open is True: - logger.info( - "Success! Bet div is now open, we can complete the bet!", - extra={"emoji": ":wrench:"}, - ) - - if div_bet_is_open is True: - decision = event.bet.calculate(event.streamer.channel_points) - if decision["choice"] is not None: - selector_index = 1 if decision["choice"] == "A" else 2 - logger.info( - f"Decision: {event.bet.get_outcome(selector_index - 1)}", - extra={"emoji": ":wrench:"}, - ) - - try: - logger.info( - f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", - extra={"emoji": ":wrench:"}, - ) - if ( - self.__send_text_on_bet( - event, selector_index, decision["amount"] - ) - is True - ): - logger.info( - f"Going to place the bet for {event}", - extra={"emoji": ":wrench:"}, - ) - if self.__click_on_vote(event, selector_index) is True: - event.bet_placed = True - time.sleep(random.uniform(5, 10)) - except Exception: - logger.error("Exception raised", exc_info=True) - else: - logger.info( - "Sorry, unable to complete the bet. The bet div is still closed!" - ) - else: - logger.info( - f"Sorry, unable to complete the bet. Event box fillable: {event.box_fillable}, the browser is betting: {self.currently_is_betting}" - ) - else: - logger.info( - f"Oh no! The event is not active anymore! Current status: {event.status}", - extra={"emoji": ":disappointed_relieved:"}, - ) - - self.browser.get("about:blank") - self.currently_is_betting = False - - def __open_coins_menu(self, event: EventPrediction) -> bool: - logger.info(f"Opening coins menu for {event}", extra={"emoji": ":wrench:"}) - status = self.__click_when_exist(Selectors.coinsMenuXP, By.XPATH) - if status is False: - status = self.__execute_script(Javascript.coinsMenu) - - if status is True: - time.sleep(random.uniform(0.01, 0.1)) - self.__debug(event, "open_coins_menu") - return True - return False - - def __click_on_bet(self, event, maximize_div=True) -> bool: - logger.info(f"Clicking on the bet for {event}", extra={"emoji": ":wrench:"}) - if self.__click_when_exist(Selectors.betTitle, By.CSS_SELECTOR) is True: - time.sleep(random.uniform(0.01, 0.1)) - if maximize_div is True: - # Edit the css for make the window full-screen in browser. Another useless change - self.__execute_script(Javascript.maximizeBetWindow, suppress_error=True) - self.__debug(event, "click_on_bet") - return True - return False - - def __enable_custom_bet_value(self, event, scroll_down=True) -> bool: - logger.info( - f"Enable input of custom value for {event}", - extra={"emoji": ":wrench:"}, - ) - - if scroll_down is True: - time.sleep(random.uniform(0.01, 0.1)) - if self.__execute_script(Javascript.scrollDownBetWindow) is False: - logger.error("Unable to scroll down in the bet window!") - - status = self.__click_when_exist(Selectors.betCustomVote, By.CSS_SELECTOR) - if status is False: - status = self.__execute_script(Javascript.betCustomVote) - - if status is True: - time.sleep(random.uniform(0.01, 0.1)) - self.__debug(event, "enable_custom_bet_value") - event.box_fillable = True - self.currently_is_betting = True - return True - else: - logger.info( - "Something went wrong unable to continue with betting - Fillable box not available!" - ) - return False - - def __send_text_on_bet(self, event, selector_index, text) -> bool: - self.__debug(event, "before__send_text") - status = self.__send_text( - f"{Selectors.betVoteInputXP}[{selector_index}]", text, By.XPATH - ) - if status is False: - status = self.__execute_script( - Javascript.betVoteInput.format(int(selector_index) - 1, int(text)) - ) - - if status is True: - self.__debug(event, "send_text") - return True - return False - - def __click_on_vote(self, event, selector_index) -> bool: - status = self.__click_when_exist( - f"{Selectors.betVoteButtonXP}[{selector_index}]", By.XPATH - ) - if status is False: - status = self.__execute_script( - Javascript.betVoteButton.format(int(selector_index) - 1) - ) - - if status is True: - self.__debug(event, "click_on_vote") - return True - return False diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 81382bd..668afee 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -50,11 +50,9 @@ def create_nonce(length=30) -> str: return nonce -def get_user_agent(browser) -> str: +def get_user_agent(browser: str) -> str: try: - return USER_AGENTS[platform.system()][ - browser.name if type(browser) != str else browser - ] + return USER_AGENTS[platform.system()][browser] except KeyError: return USER_AGENTS["Linux"]["FIREFOX"] diff --git a/example.py b/example.py index 4b1ae3f..fcb7cf4 100644 --- a/example.py +++ b/example.py @@ -5,7 +5,6 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings -from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", @@ -17,11 +16,6 @@ twitch_miner = TwitchChannelPointsMiner( emoji=True, # On Windows we have a problem to print emoji. Set to false if you have a problem less=False # If you think that the logs are too much verborse set this to True ), - browser_settings=BrowserSettings( - browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser - show=False, # Show the browser during bet else headless mode - do_screenshot=False, # Do screenshot during the bet - ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction follow_raid=True, # Follow raid to obtain more points diff --git a/requirements.txt b/requirements.txt index c9b056d..46f7d58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ requests websocket-client browser_cookie3 pillow -selenium python-dateutil emoji millify From 78b64ca3b27b625b5431dc509472e794c1501e65 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Fri, 5 Feb 2021 08:47:07 +0100 Subject: [PATCH 037/124] Remove test log & use category PREDICTION instead of PREDICTION-TEST --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index d860865..61e5bb8 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -281,23 +281,17 @@ class WebSocketsPool: "gained": points_gained, } ws.streamers[streamer_index].update_history( - "PREDICTION-TEST", points_gained + "PREDICTION", points_gained ) # Remove duplicate history records from previous message sent in community-points-user-v1 if result_type == "REFUND": - logger.info( - "REMOVE REFUND FROM POINTS MSG" - ) # TODO remove ws.streamers[streamer_index].update_history( "REFUND", -points_placed, counter=-1, ) elif result_type == "WIN": - logger.info( - "REMOVE PREDICTION FROM POINTS MSG" - ) # TODO remove ws.streamers[streamer_index].update_history( "PREDICTION", -points_won, From 2f336699af988ad123e89613b9cc16d731bcd64f Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 5 Feb 2021 13:02:39 +0100 Subject: [PATCH 038/124] Strange error founded not related with the ws. json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1). In debug print the text, so wen se what happened --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2d6a040..9c97672 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -94,7 +94,7 @@ class Twitch: }, ) logger.debug( - f"Data: {json_data}, Status code: {response.status_code}, Content: {response.json()}" + f"Data: {json_data}, Status code: {response.status_code}, Content: {response.text}" ) return response.json() From 256fae52a4608de028f035294f884994fab63ef1 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Fri, 5 Feb 2021 16:08:51 +0100 Subject: [PATCH 039/124] Display the owner of a prediction --- TwitchChannelPointsMiner/classes/entities/EventPrediction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py index 43da5f4..cfc5d13 100644 --- a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py +++ b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py @@ -30,11 +30,11 @@ class EventPrediction: self.bet = Bet(outcomes, streamer.settings.bet) def __repr__(self): - return f"EventPrediction(event_id={self.event_id}, title={self.title})" + return f"EventPrediction(event_id={self.event_id}, streamer={self.streamer}, title={self.title})" def __str__(self): return ( - f"EventPrediction: {self.title}" + f"EventPrediction: {self.streamer} - {self.title}" if Settings.logger.less else self.__repr__() ) From 7a895bf9c38a56e12df1bbe1b204449fbbff07fc Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 16:45:34 +0100 Subject: [PATCH 040/124] Clear code, update readme --- README.md | 17 ++--- .../TwitchChannelPointsMiner.py | 6 -- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- .../classes/WebSocketsPool.py | 2 +- .../classes/entities/Stream.py | 2 +- .../classes/entities/Streamer.py | 2 +- .../{constants/twitch.py => constants.py} | 11 ++++ .../constants/__init__.py | 0 TwitchChannelPointsMiner/constants/browser.py | 65 ------------------- TwitchChannelPointsMiner/utils.py | 2 +- 10 files changed, 21 insertions(+), 88 deletions(-) rename TwitchChannelPointsMiner/{constants/twitch.py => constants.py} (84%) delete mode 100644 TwitchChannelPointsMiner/constants/__init__.py delete mode 100644 TwitchChannelPointsMiner/constants/browser.py diff --git a/README.md b/README.md index 8efee41..538af39 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,7 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - Automatic download the followers list and use as input - Better 'Watch Streak' strategy in priority system [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) - Auto claim game drops from Twitch inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) Read more about game drops [here](https://help.twitch.tv/s/article/mission-based-drops) -- Place the bet / make prediction and won or lose (good luck) your channel points! - -For the bet system the script use Selenium. Could be usefull understand how to MakePrediction usign a [POST] request. I've also write a [poc](/TwitchChannelPointsMiner/classes/Twitch.py#L160) but I don't know how to calculate/create the transactionID. Any helps are welcome +- Place the bet / make prediction and won or lose (good luck) your channel points! - Without Browser! Thanks to @lay295 [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ### Full logs ``` @@ -45,10 +43,8 @@ For the bet system the script use Selenium. Could be usefull understand how to M %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 - [place_bet]: 🔧 Going to complete bet 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 - [place_bet]: 🔧 Decision: YES (PINK), Points: 156k, Users: 46 (61.33%), Odds: 1.57 (63.69%) -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Going to write: 4296 channel points on input B -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Going to place the bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) +%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. @@ -78,10 +74,8 @@ For the bet system the script use Selenium. Could be usefull understand how to M %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 - 🔧 Decision: YES (PINK), Points: 156k, Users: 46 (61.33%), Odds: 1.57 (63.69%) -%d/%m %H:%M:%S - 🔧 Going to write: 4296 channel points on input B -%d/%m %H:%M:%S - 🔧 Going to place the bet for EventPrediction: Please star this repo +%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. @@ -302,7 +296,6 @@ If you already have a `twitch-cookies.pkl` and you don't want to login again ple Other users have find multiple problems on Windows my suggestion are: - Stop use Windows :stuck_out_tongue_closed_eyes: - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` - - Download the geckodriver from here: https://github.com/mozilla/geckodriver/releases/ and extract in the same folder of this project. For other issue with geckodriver just googling: https://stackoverflow.com/questions/40208051/selenium-using-python-geckodriver-executable-needs-to-be-in-path Other usefully infos can be founded here: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner/issues/31 ## Issue / Debug diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 7cf8bf1..941b637 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -28,11 +28,6 @@ from TwitchChannelPointsMiner.utils import ( set_default_settings, ) -# Suppress warning for urllib3.connectionpool (selenium close connection) -# Suppress also the selenium logger please -logging.getLogger("urllib3").setLevel(logging.ERROR) -logging.getLogger("selenium").setLevel(logging.ERROR) - logger = logging.getLogger(__name__) @@ -41,7 +36,6 @@ class TwitchChannelPointsMiner: self, username: str, claim_drops_startup: bool = False, - # Settings for logging and selenium as you can see. # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), # Default values for all streamers diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 0eab361..b9595a0 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -22,7 +22,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import ( ) from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants.twitch import API, CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.constants import API, CLIENT_ID, GQLOperations from TwitchChannelPointsMiner.utils import _millify logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 73474a5..12b61c0 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -11,7 +11,7 @@ from TwitchChannelPointsMiner.classes.entities.Message import Message from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket -from TwitchChannelPointsMiner.constants.twitch import WEBSOCKET +from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import _millify, get_streamer_index logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index ad68d4d..affce68 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -4,7 +4,7 @@ import time from base64 import b64encode from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.constants.twitch import DROP_ID +from TwitchChannelPointsMiner.constants import DROP_ID logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index a97b774..3eee2b6 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -4,7 +4,7 @@ import time from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings from TwitchChannelPointsMiner.classes.entities.Stream import Stream from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.constants.twitch import URL +from TwitchChannelPointsMiner.constants import URL from TwitchChannelPointsMiner.utils import _millify logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/constants/twitch.py b/TwitchChannelPointsMiner/constants.py similarity index 84% rename from TwitchChannelPointsMiner/constants/twitch.py rename to TwitchChannelPointsMiner/constants.py index 9e26837..4969f08 100644 --- a/TwitchChannelPointsMiner/constants/twitch.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,6 +5,17 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" +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", + "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", + }, +} + class GQLOperations: url = "https://gql.twitch.tv/gql" diff --git a/TwitchChannelPointsMiner/constants/__init__.py b/TwitchChannelPointsMiner/constants/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/TwitchChannelPointsMiner/constants/browser.py b/TwitchChannelPointsMiner/constants/browser.py deleted file mode 100644 index 9565af5..0000000 --- a/TwitchChannelPointsMiner/constants/browser.py +++ /dev/null @@ -1,65 +0,0 @@ -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", - "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", - }, -} - - -class Selectors: - # XPath Selector and Javascript helpers - cookiePolicy = 'button[data-a-target="consent-banner-accept"]' - coinsMenuXP = '//div[@data-test-selector="community-points-summary"]//button' - betTitle = '[data-test-selector="predictions-list-item__title"]' - betCustomVote = "button[data-test-selector='prediction-checkout-active-footer__input-type-toggle']" - betMainDivXP = "//div[@id='channel-points-reward-center-body']//div[contains(@class,'custom-prediction-button')]" - betVoteInputXP = f"({betMainDivXP}//input)" - betVoteButtonXP = f"({betMainDivXP}//button)" - - -class Javascript: - # Helpers for selenium. I think is very useless - coinsMenu = 'document.querySelector("[data-test-selector=\'community-points-summary\']").getElementsByTagName("button")[0].click();' - betVoteInput = 'document.getElementById("channel-points-reward-center-body").getElementsByTagName("input")[{}].value = {};' - betVoteButton = 'document.getElementById("channel-points-reward-center-body").getElementsByTagName("button")[{}].click();' - betCustomVote = f'document.querySelector("{Selectors.betCustomVote}").click();' - - # Some Javascript code that should help the script - localStorage = """ - window.localStorage.setItem("volume", 0); - window.localStorage.setItem("channelPointsOnboardingDismissed", true); - window.localStorage.setItem("twilight.theme", 1); - window.localStorage.setItem("mature", true); - window.localStorage.setItem("rebrand-notice-dismissed", true); - window.localStorage.setItem("emoteAnimationsEnabled", false); - window.localStorage.setItem("chatPauseSetting", "ALTKEY"); - """ - clearStyleChat = """ - var item = document.querySelector('[data-test-selector="chat-scrollable-area__message-container"]'); - if (item) { - var parent = item.closest("div.simplebar-scroll-content"); - if(parent) parent.hidden = true; - } - var header = document.querySelector('[data-test-selector="channel-leaderboard-container"]'); - if(header) header.hidden = true; - """ - maximizeBetWindow = """ - var absolute = document.querySelector('[aria-describedby="channel-points-reward-center-body"]').closest("div.tw-absolute") - if(absolute) absolute.classList.remove("tw-absolute") - - document.getElementsByClassName("reward-center__content")[0].style.width = "44rem"; - document.getElementsByClassName("reward-center__content")[0].style.height = "55rem"; - - document.querySelector('[aria-describedby="channel-points-reward-center-body"]').style["max-height"] = "55rem"; - - document.getElementsByClassName("reward-center-body")[0].style["max-width"] = "44rem"; - // document.getElementsByClassName("reward-center-body")[0].style["min-height"] = "55rem"; - """ - scrollDownBetWindow = """ - var scrollable = document.getElementById("channel-points-reward-center-body").closest("div.simplebar-scroll-content"); - scrollable.scrollTop = scrollable.scrollHeight; - """ diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index ef94819..26d2483 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -7,7 +7,7 @@ from random import randrange from millify import millify -from TwitchChannelPointsMiner.constants.browser import USER_AGENTS +from TwitchChannelPointsMiner.constants import USER_AGENTS def _millify(input, precision=2): From b3d845c61e502385b73bfae2fc1e7c1150d904b1 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 17:01:24 +0100 Subject: [PATCH 041/124] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 538af39..eb135b0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - Automatic download the followers list and use as input - Better 'Watch Streak' strategy in priority system [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) - Auto claim game drops from Twitch inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) Read more about game drops [here](https://help.twitch.tv/s/article/mission-based-drops) -- Place the bet / make prediction and won or lose (good luck) your channel points! - Without Browser! Thanks to @lay295 [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) +- Place the bet / make prediction and won or lose (🍀) your channel points! +No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ([@lay295](https://github.com/lay295)) ### Full logs ``` From 8b11241e8b804aa4ea08836b877af566913c8f53 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 17:02:48 +0100 Subject: [PATCH 042/124] Update __init__.py --- TwitchChannelPointsMiner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index a37153e..4c150ca 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "2.5.0" +__version__ = "2.7.1" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ From 8205f019de587813cd49b0cc35c80d4e0d1b0429 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 19:55:33 +0100 Subject: [PATCH 043/124] Fix [] vs None --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 12b61c0..881e2d4 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) class WebSocketsPool: def __init__(self, twitch, streamers, events_predictions): - self.ws = None + self.ws = [] self.twitch = twitch self.streamers = streamers self.events_predictions = events_predictions From 6a3c81c0cf6f11961953d840dbfd3ff8a47eec2a Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 22:00:57 +0100 Subject: [PATCH 044/124] update example --- README.md | 10 +++++----- example.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cb9dab2..4028244 100644 --- a/README.md +++ b/README.md @@ -196,11 +196,11 @@ twitch_miner = TwitchChannelPointsMiner( 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(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), - 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(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), - 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(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), - 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(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), + 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"), diff --git a/example.py b/example.py index f56cd0c..c520299 100644 --- a/example.py +++ b/example.py @@ -52,11 +52,11 @@ twitch_miner = TwitchChannelPointsMiner( 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(key=OutcomeKeys.TOTAL_USERS, condition=Condition.LTE, value=800, decision=False) ) )), - 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(key=OutcomeKeys.TOTAL_POINTS, condition=Condition.GTE, value=250, decision=False) ) )), - 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(key=OutcomeKeys.ODDS, condition=Condition.LT, value=300, decision=True) ) )), - 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(key=OutcomeKeys.PERCENTAGE_USERS, condition=Condition.GTE, value=300, decision=True) ) )), + 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"), From 745c80a19b3e502b0b17e1e962dd7950a90f4d0b Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 6 Feb 2021 22:04:13 +0100 Subject: [PATCH 045/124] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4028244..8c33b4b 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ twitch_miner = TwitchChannelPointsMiner( stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 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, # The key must be [GT, LT, GTE, LTE] than value + where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value value=800 ) ) @@ -290,7 +290,7 @@ In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot wi | `where` | Condition | None | Condition that should match for place bet | | `value` | number | None | Value to compare | -Allowed values for `key` are: +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] @@ -300,7 +300,7 @@ Allowed values for `key` are: - `TOTAL_USERS` (sum) - `TOTAL_POINTS` (sum) -Allowed values for `condition` are: `GT, LT, GTE, LTE` +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 are greater than 200 From b08285651ad28e4f866287debaefbdf9b9a7cbbb Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 15:28:01 +0100 Subject: [PATCH 046/124] too many distractions - Close #57 #58 --- README.md | 2 +- TwitchChannelPointsMiner/classes/entities/Bet.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8c33b4b..66a0841 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,7 @@ Allowed values for `by` are: - `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 points) +- `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) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 877028a..21135d1 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -31,12 +31,18 @@ class Condition(Enum): 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" + # TOTAL_ and DECISION refer to same key / values + # But we have different name for help us in filter + DECISION_USERS = "total_users" + DECISION_POINTS = "total_points" class FilterCondition(object): @@ -46,7 +52,7 @@ class FilterCondition(object): self.value = value def __repr__(self): - return f"FilterCondition(By={self.key}, Where={self.condition}, Value={self.value})" + return f"FilterCondition(By={self.by}, Where={self.where}, Value={self.value})" class BetSettings(object): From 669f7a9212d558cb38a4f14b2ff258e48dcc9a91 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 15:31:57 +0100 Subject: [PATCH 047/124] Make sure to have integer amount --- TwitchChannelPointsMiner/classes/entities/Bet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 21135d1..bf44702 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -241,4 +241,5 @@ class Bet(object): self.decision["amount"] = ( self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount ) + self.decision["amount"] = int(self.decision["amount"]) return self.decision From 07515e92c9929f49ba005cf4dd35d5b5bf9d269e Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 15:54:31 +0100 Subject: [PATCH 048/124] If the user is online but the last updated was performed more than 10 minutes ago attempt to perform a force update. #54 --- TwitchChannelPointsMiner/classes/Twitch.py | 13 ++++++++++++- TwitchChannelPointsMiner/classes/entities/Stream.py | 5 ++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index b9595a0..abe3d8f 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -234,6 +234,12 @@ class Twitch(object): ) ] + 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]) + """ Check if we need need to change priority based on watch streak Viewers receive points for returning for x consecutive streams. @@ -266,7 +272,12 @@ class Twitch(object): while len(streamers_watching) < 2 and len(streamers_index) > 1: another_streamer_index = streamers_index.pop(0) if another_streamer_index not in streamers_watching: - streamers_watching.append(another_streamer_index) + try: + streamers_watching.append(another_streamer_index) + except requests.exceptions.ConnectionError as e: + logger.error( + f"Error while trying to perform a force for streamer's update: {e}" + ) """ Twitch has a limit - you can't watch more than 2 channels at one time. diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index affce68..59ddb1f 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -63,7 +63,10 @@ class Stream(object): return None if self.game in [{}, None] else self.game["name"] def update_required(self): - return self.__last_update == 0 or (time.time() - self.__last_update) >= 120 + 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 From fb36fa3dbbf67be5b1ad79ea05d6c52459d41f96 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 16:19:39 +0100 Subject: [PATCH 049/124] On Exception is_closed=True. Rety after x random minutes if the internet connection It's not available --- .../classes/TwitchWebSocket.py | 10 +++++++--- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 14 +++++++++++++- TwitchChannelPointsMiner/utils.py | 10 ++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index aaf595c..f87aff1 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -3,6 +3,7 @@ import logging import time from websocket import WebSocketApp +from websocket.exceptions import WebSocketConnectionClosedException from TwitchChannelPointsMiner.utils import create_nonce @@ -52,9 +53,12 @@ class TwitchWebSocket(WebSocketApp): self.last_ping = time.time() def send(self, request): - request_str = json.dumps(request, separators=(",", ":")) - logger.debug(f"#{self.index} - Send: {request_str}") - super().send(request_str) + 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 diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 881e2d4..289cef8 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -12,7 +12,11 @@ from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET -from TwitchChannelPointsMiner.utils import _millify, get_streamer_index +from TwitchChannelPointsMiner.utils import ( + _millify, + check_internet_connection, + get_streamer_index, +) logger = logging.getLogger(__name__) @@ -32,6 +36,7 @@ class WebSocketsPool: """ def submit(self, topic): + # Check if we need to create a new WebSocket instance if self.ws == [] or self.ws[-1] is None or len(self.ws[-1].topics) >= 50: self.append_new_websocket() @@ -112,6 +117,13 @@ class WebSocketsPool: ) time.sleep(30) + while check_internet_connection() 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) + self = ws.parent_pool self.ws[ws.index] = None for topic in ws.topics: diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 26d2483..eb97098 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -1,5 +1,6 @@ import platform import re +import socket import time from copy import deepcopy from datetime import datetime, timezone @@ -130,3 +131,12 @@ def set_default_settings(settings, defaults): def char_decision_as_index(char): return 0 if char == "A" else 1 + + +def check_internet_connection(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 From 5d2bcf7324a06b6b7c31f72f9a9d04655e3d0081 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 21:10:22 +0100 Subject: [PATCH 050/124] Forgot to call skip() in only-request bet. My fault. Close #60 --- TwitchChannelPointsMiner/classes/Twitch.py | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index b9595a0..7bd3937 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -206,21 +206,26 @@ class Twitch(object): f"Going to complete bet for {event} owned by {event.streamer}", extra={"emoji": ":four_leaf_clover:"}, ) - logger.info( - f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", - extra={"emoji": ":four_leaf_clover:"}, - ) - json_data = copy.deepcopy(GQLOperations.MakePrediction) - json_data["variables"] = { - "input": { - "eventID": event.event_id, - "outcomeID": decision["id"], - "points": decision["amount"], - "transactionID": token_hex(16), + if event.bet.skip() is True: + logger.info(f"Skip betting for the event {event}") + logger.info(f"Skip settings {event.bet.settings.filter_condition}") + else: + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) + + json_data = copy.deepcopy(GQLOperations.MakePrediction) + json_data["variables"] = { + "input": { + "eventID": event.event_id, + "outcomeID": decision["id"], + "points": decision["amount"], + "transactionID": token_hex(16), + } } - } - return self.post_gql_request(json_data) + return self.post_gql_request(json_data) def send_minute_watched_events(self, streamers, watch_streak=False, chunk_size=3): while self.running: From 225e89a04bbf7d12094d347b227d291a83c47b99 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 7 Feb 2021 21:16:19 +0100 Subject: [PATCH 051/124] Print filter condition in finally recap. Close #61 --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 941b637..0c3d472 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -272,8 +272,18 @@ class TwitchChannelPointsMiner: ): logger.info( f"{self.events_predictions[event_id].streamer.settings.bet}", - extra={"emoji": ":bar_chart:"}, + extra={"emoji": ":gear:"}, ) + if ( + self.events_predictions[ + event_id + ].streamer.settings.bet.filter_condition + is not None + ): + logger.info( + f"{self.events_predictions[event_id].streamer.settings.bet.filter_condition}", + extra={"emoji": ":pushpin:"}, + ) logger.info( f"{self.events_predictions[event_id].print_recap()}", extra={"emoji": ":bar_chart:"}, From e6447da26ecc295b44cdf321bc722378e28dc9b6 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 08:27:50 +0100 Subject: [PATCH 052/124] Remember to check if the events It's still ACTIVE --- TwitchChannelPointsMiner/classes/Twitch.py | 42 +++++++++++++--------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 7bd3937..8db5d4a 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -206,27 +206,37 @@ class Twitch(object): f"Going to complete bet for {event} owned by {event.streamer}", extra={"emoji": ":four_leaf_clover:"}, ) + if event.status == "ACTIVE": + if event.bet.skip() is True: + logger.info( + f"Skip betting for the event {event}", extra={"emoji": ":pushpin:"} + ) + logger.info( + f"Skip settings {event.bet.settings.filter_condition}", + extra={"emoji": ":pushpin:"}, + ) + else: + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) - if event.bet.skip() is True: - logger.info(f"Skip betting for the event {event}") - logger.info(f"Skip settings {event.bet.settings.filter_condition}") + json_data = copy.deepcopy(GQLOperations.MakePrediction) + json_data["variables"] = { + "input": { + "eventID": event.event_id, + "outcomeID": decision["id"], + "points": decision["amount"], + "transactionID": token_hex(16), + } + } + return self.post_gql_request(json_data) else: logger.info( - f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", - extra={"emoji": ":four_leaf_clover:"}, + f"Oh no! The event is not active anymore! Current status: {event.status}", + extra={"emoji": ":disappointed_relieved:"}, ) - json_data = copy.deepcopy(GQLOperations.MakePrediction) - json_data["variables"] = { - "input": { - "eventID": event.event_id, - "outcomeID": decision["id"], - "points": decision["amount"], - "transactionID": token_hex(16), - } - } - return self.post_gql_request(json_data) - def send_minute_watched_events(self, streamers, watch_streak=False, chunk_size=3): while self.running: streamers_index = [ From 8d00d6da6f0768fe484b06e50993c484e8fa142b Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 08:37:13 +0100 Subject: [PATCH 053/124] Reduce prediction window by 3/6s - Collect more accurate data for decision --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 881e2d4..e49b3c2 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -198,9 +198,8 @@ class WebSocketsPool: prediction_window_seconds = float( event_dict["prediction_window_seconds"] ) - prediction_window_seconds -= ( - 30 if prediction_window_seconds <= 120 else 60 - ) + # Reduce prediction window by 3/6s - Collect more accurate data for decision + prediction_window_seconds -= random.uniform(3, 6) event = EventPrediction( ws.streamers[streamer_index], event_id, From 30813c30132115d29e2640e7c74694c265fc7ebc Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 08:39:13 +0100 Subject: [PATCH 054/124] Why the gear is so ungly? --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 0c3d472..a9d35a0 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -272,7 +272,7 @@ class TwitchChannelPointsMiner: ): logger.info( f"{self.events_predictions[event_id].streamer.settings.bet}", - extra={"emoji": ":gear:"}, + extra={"emoji": ":wrench:"}, ) if ( self.events_predictions[ From f61ccfc2fd6be0d1d7bafe6d9bcc4c3dec6e6659 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 08:42:19 +0100 Subject: [PATCH 055/124] We have already the streamer in EventPrediction. Remove from recap --- .../TwitchChannelPointsMiner.py | 44 ++++++++++--------- .../classes/entities/EventPrediction.py | 2 +- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index a9d35a0..b8ac1d8 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -264,32 +264,36 @@ class TwitchChannelPointsMiner: extra={"emoji": ":hourglass:"}, ) - for event_id in self.events_predictions: - if ( - self.events_predictions[event_id].bet_confirmed is True - and self.events_predictions[event_id].streamer.settings.make_predictions - is True - ): - logger.info( - f"{self.events_predictions[event_id].streamer.settings.bet}", - extra={"emoji": ":wrench:"}, - ) + if self.events_predictions != {}: + print("") + for event_id in self.events_predictions: if ( - self.events_predictions[ + self.events_predictions[event_id].bet_confirmed is True + and self.events_predictions[ event_id - ].streamer.settings.bet.filter_condition - is not None + ].streamer.settings.make_predictions + is True ): logger.info( - f"{self.events_predictions[event_id].streamer.settings.bet.filter_condition}", - extra={"emoji": ":pushpin:"}, + f"{self.events_predictions[event_id].streamer.settings.bet}", + extra={"emoji": ":wrench:"}, + ) + if ( + self.events_predictions[ + event_id + ].streamer.settings.bet.filter_condition + is not None + ): + logger.info( + f"{self.events_predictions[event_id].streamer.settings.bet.filter_condition}", + extra={"emoji": ":pushpin:"}, + ) + logger.info( + f"{self.events_predictions[event_id].print_recap()}", + extra={"emoji": ":bar_chart:"}, ) - logger.info( - f"{self.events_predictions[event_id].print_recap()}", - extra={"emoji": ":bar_chart:"}, - ) - print("") + print("") for streamer_index in range(0, len(self.streamers)): logger.info( f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(self.streamers[streamer_index].channel_points - self.original_streamers[streamer_index].channel_points)}", diff --git a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py index 981e0df..a55f520 100644 --- a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py +++ b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py @@ -46,4 +46,4 @@ class EventPrediction(object): return float_round(self.prediction_window_seconds - self.elapsed(timestamp)) def print_recap(self) -> str: - return f"{self}\n\t\t{self.streamer}\n\t\t{self.bet}\n\t\tResult: {self.final_result}" + return f"{self}\n\t\t{self.bet}\n\t\tResult: {self.final_result}" From ff919d8d379461a51378882cd23a42a25eb21887 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 10:57:58 +0100 Subject: [PATCH 056/124] Update README.md --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 439a61f..2ca928c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ > 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 channels point [here](https://help.twitch.tv/s/article/channel-points-guide) +## Community +If you have any type of issue, you need help, or you just want to suggest a new feature please open a GitHub Issue. Don't write me on [Instagram](https://www.instagram.com/tkd_alex/), [Telegram](https://t.me/TkdAlex), [Discord](https://discordapp.com/users/641397388132483121), [Twitter](https://twitter.com/TkdAxel) (but you can follow me 😆) or somewhere else. If you don't have an account on this platform you can create, It's free. I do not want to be rude, but if you have a problem, maybe another user can have also the same problem and your issue can help the community. Same for the new feature, your idea can help other users, and It's beautiful to discuss between us. + +If you want to help on this project please leave a star 🌟 and share with your friends! 😎 ## Main difference from the original repository: @@ -29,6 +33,7 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - Place the bet / make prediction and won or lose (🍀) your channel points! No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ([@lay295](https://github.com/lay295)) +## Logs feature ### Full logs ``` %d/%m/%y %H:%M:%S - INFO - [run]: 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' @@ -298,13 +303,17 @@ Other users have find multiple problems on Windows my suggestion are: - Stop use Windows :stuck_out_tongue_closed_eyes: - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` -Other usefully infos can be founded here: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner/issues/31 +Other usefully infos can be founded 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=hoPyNwAk97U&t=1s). It's for the first version of the miner, but the setup It's the same. ## Issue / Debug -When you open a new issue please use the correct template. +When you open a new issue please use the correct **template**. Please provide at least the following information/files: - Operation System - Python Version -- logs/ `LoggerSettings(file_level=logging.DEBUG)` +- Log debug file `LoggerSettings(file_level=logging.DEBUG)` Make sure also to have the latest commit. From 82cd573d9158b4597dfb4d9fac8852d30e123171 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 11:09:42 +0100 Subject: [PATCH 057/124] Update README.md --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 2ca928c..09ddf5d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,29 @@ > 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 channels point [here](https://help.twitch.tv/s/article/channel-points-guide) + +# README Contents +1. 🤝 [Community](#community) +2. 🚀 [Main difference from the original repository](#main-difference-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) + - [Limits](#limits) +5. 🔧 [Settings](#settings) + - [LoggerSettings](#loggersettings) + - [StreamerSettings](#streamersettings) + - [BetSettings](#betsettings) + - [Bet strategy](#bet-strategy) + - [FilterCondition](#filtercondition) + - [Example](#example) +6. 🍪 [Migrating from old repository (the original one)](#migrating-from-old-repository-the-original-one) +7. 🪟 [Windows](#windows) +8. 🐛 [Issue / Debug](#issue--debug) +9. ⚠️ [Disclaimer](#disclaimer) + + ## Community If you have any type of issue, you need help, or you just want to suggest a new feature please open a GitHub Issue. Don't write me on [Instagram](https://www.instagram.com/tkd_alex/), [Telegram](https://t.me/TkdAlex), [Discord](https://discordapp.com/users/641397388132483121), [Twitter](https://twitter.com/TkdAxel) (but you can follow me 😆) or somewhere else. If you don't have an account on this platform you can create, It's free. I do not want to be rude, but if you have a problem, maybe another user can have also the same problem and your issue can help the community. Same for the new feature, your idea can help other users, and It's beautiful to discuss between us. From 2a5a7b2b55e9ebe58427b5ffea6088d8d124a2f0 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 13:04:52 +0100 Subject: [PATCH 058/124] Replace total with decision and if I need to use the key replace again. So we printing the same value but use in dict reference another one. Fix #63 --- .../classes/entities/Bet.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index bf44702..6070e7a 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -39,10 +39,9 @@ class OutcomeKeys(object): # Real key on Bet dict [''] - Sum() TOTAL_USERS = "total_users" TOTAL_POINTS = "total_points" - # TOTAL_ and DECISION refer to same key / values - # But we have different name for help us in filter - DECISION_USERS = "total_users" - DECISION_POINTS = "total_points" + # This key does not exist + DECISION_USERS = "decision_users" + DECISION_POINTS = "decision_points" class FilterCondition(object): @@ -52,7 +51,7 @@ class FilterCondition(object): self.value = value def __repr__(self): - return f"FilterCondition(By={self.by}, Where={self.where}, Value={self.value})" + return f"FilterCondition(By={self.by.upper()}, Where={self.where}, Value={self.value})" class BetSettings(object): @@ -180,14 +179,22 @@ class Bet(object): key = self.settings.filter_condition.by condition = self.settings.filter_condition.where value = self.settings.filter_condition.value - compared_value = ( - (self.outcomes[0][key] + self.outcomes[1][key]) - if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS] - else self.outcomes[char_decision_as_index(self.decision["choice"])][key] + + 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"]) + compared_value = self.outcomes[outcome_index][fixed_key] logger.info( - f"Filter applied on this bet. Current {key} is {compared_value}, must be {condition} {value}" + f"Filter applied on this bet. Current {key.upper()} is {compared_value}, must be {condition} {value}" ) # Check if condition is satisfied if condition == Condition.GT: From 88d589aa40c455afeefbc7409eaee8c64b1e10cd Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 13:06:54 +0100 Subject: [PATCH 059/124] We don't need anymore the sleep for handling browser clear close. Sorry for this --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index b8ac1d8..b85e960 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -244,9 +244,9 @@ class TwitchChannelPointsMiner: self.ws_pool.end() self.minute_watcher_thread.join() + time.sleep(1) self.__print_report() - time.sleep(3.5) # Do sleep for ending threads ... sys.exit(0) From 86c28673d13ccb15682634426ddced1b65f4d895 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 8 Feb 2021 20:59:36 +0100 Subject: [PATCH 060/124] WebSocketConnectionClosedException is inside websocket and not inside websocket.exceptions --- TwitchChannelPointsMiner/classes/TwitchWebSocket.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index f87aff1..2e85bc5 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -2,8 +2,7 @@ import json import logging import time -from websocket import WebSocketApp -from websocket.exceptions import WebSocketConnectionClosedException +from websocket import WebSocketApp, WebSocketConnectionClosedException from TwitchChannelPointsMiner.utils import create_nonce From 696a78197ce0792f071ff8b9e9463c47585cff37 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 9 Feb 2021 10:44:35 +0100 Subject: [PATCH 061/124] Attempt to handle all connection error/exception. Return {} in case of exception and check correctly with If statement if we are right or not. Don't recconect with lost PING if connection in not available --- .../TwitchChannelPointsMiner.py | 7 +- TwitchChannelPointsMiner/classes/Twitch.py | 105 +++++++++++------- .../classes/WebSocketsPool.py | 7 +- TwitchChannelPointsMiner/utils.py | 2 +- 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 941b637..1f1d10c 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -25,6 +25,7 @@ from TwitchChannelPointsMiner.utils import ( _millify, at_least_one_value_in_settings_is, get_user_agent, + internet_connection_available, set_default_settings, ) @@ -172,6 +173,7 @@ class TwitchChannelPointsMiner: ), ), ) + self.minute_watcher_thread.name = "Minute watcher" self.minute_watcher_thread.start() self.ws_pool = WebSocketsPool( @@ -230,10 +232,11 @@ class TwitchChannelPointsMiner: for index in range(0, len(self.ws_pool.ws)): if ( self.ws_pool.ws[index] is not None - and self.ws_pool.ws[index].elapsed_last_ping() > 10 + and self.ws_pool.ws[index].elapsed_last_ping() > 15 + and internet_connection_available() is True ): logger.info( - f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..." + f"#{index} - The last PING was sent more than 15 minutes ago. Reconnecting to the WebSocket..." ) WebSocketsPool.handle_reconnection(self.ws_pool.ws[index]) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index abe3d8f..886f1b4 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -23,7 +23,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import ( from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin from TwitchChannelPointsMiner.constants import API, CLIENT_ID, GQLOperations -from TwitchChannelPointsMiner.utils import _millify +from TwitchChannelPointsMiner.utils import _millify, internet_connection_available logger = logging.getLogger(__name__) @@ -74,50 +74,63 @@ class Twitch(object): ] def get_spade_url(self, streamer): - headers = {"User-Agent": self.user_agent} - main_page_request = requests.get(streamer.streamer_url, headers=headers) - response = main_page_request.text - settings_url = re.search( - "(https://static.twitchcdn.net/config/settings.*?js)", response - ).group(1) + try: + headers = {"User-Agent": self.user_agent} + main_page_request = requests.get(streamer.streamer_url, headers=headers) + response = main_page_request.text + settings_url = re.search( + "(https://static.twitchcdn.net/config/settings.*?js)", response + ).group(1) - settings_request = requests.get(settings_url, headers=headers) - response = settings_request.text - streamer.stream.spade_url = re.search('"spade_url":"(.*?)"', response).group(1) + settings_request = requests.get(settings_url, headers=headers) + response = settings_request.text + streamer.stream.spade_url = re.search( + '"spade_url":"(.*?)"', response + ).group(1) + except requests.exceptions.RequestException as e: + logger.error(f"Something went wrong during extraction of 'spade_url': {e}") def post_gql_request(self, json_data): - response = requests.post( - GQLOperations.url, - json=json_data, - headers={ - "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", - "Client-Id": CLIENT_ID, - "User-Agent": self.user_agent, - }, - ) - logger.debug( - f"Data: {json_data}, Status code: {response.status_code}, Content: {response.text}" - ) - return response.json() + try: + response = requests.post( + GQLOperations.url, + json=json_data, + headers={ + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + "User-Agent": self.user_agent, + }, + ) + 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 {} 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) - stream = response["data"]["user"]["stream"] - if stream is not None: - return stream["id"] - else: - raise StreamerIsOfflineException + 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["data"]["user"]["stream"] is None: - raise StreamerIsOfflineException - else: - return response["data"]["user"] + 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: @@ -181,7 +194,7 @@ class Twitch(object): def __get_inventory(self): response = self.post_gql_request(GQLOperations.Inventory) - return response["data"]["currentUser"]["inventory"] + return response["data"]["currentUser"]["inventory"] if response != {} else {} # Load the amount of current points for a channel, check if a bonus is available def load_channel_points_context(self, streamer): @@ -189,14 +202,15 @@ class Twitch(object): json_data["variables"] = {"channelLogin": streamer.username} response = self.post_gql_request(json_data) - if response["data"]["community"] is None: - raise StreamerDoesNotExistException - channel = response["data"]["community"]["channel"] - community_points = channel["self"]["communityPoints"] - streamer.channel_points = community_points["balance"] + 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"] - if community_points["availableClaim"] is not None: - self.claim_bonus(streamer, community_points["availableClaim"]["id"]) + 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) @@ -299,9 +313,18 @@ class Twitch(object): ) if response.status_code == 204: streamers[index].stream.update_minute_watched() - except requests.exceptions.ConnectionError as e: + except requests.exceptions.RequestException as e: logger.error(f"Error while trying to watch a minute: {e}") + # 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" + ) + time.sleep(random_sleep * 60) + # Create chunk of sleep of speed-up the break loop after CTRL+C sleep_time = max(next_iteration - time.time(), 0) / chunk_size for i in range(0, chunk_size): diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 289cef8..5bfa7d4 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -14,8 +14,8 @@ from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import ( _millify, - check_internet_connection, get_streamer_index, + internet_connection_available, ) logger = logging.getLogger(__name__) @@ -63,6 +63,7 @@ class WebSocketsPool: self.thread_ws = threading.Thread(target=lambda: self.ws[-1].run_forever()) self.thread_ws.daemon = True + self.thread_ws.name = f"WebSocket #{self.ws[-1].index}" self.thread_ws.start() def end(self): @@ -85,7 +86,7 @@ class WebSocketsPool: if ws.elapsed_last_pong() > 10 and ws.is_reconneting is False: logger.info( - f"#{ws.index} - The last PONG was received more than 10 minutes ago. Reconnect the WebSocket" + f"#{ws.index} - The last PONG was received more than 10 minutes ago" ) ws.is_reconneting = True WebSocketsPool.handle_reconnection(ws) @@ -117,7 +118,7 @@ class WebSocketsPool: ) time.sleep(30) - while check_internet_connection() is False: + 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" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index eb97098..da8de53 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -133,7 +133,7 @@ def char_decision_as_index(char): return 0 if char == "A" else 1 -def check_internet_connection(host="8.8.8.8", port=53, timeout=3): +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)) From 3ad59c5c3855135c4e2c2a3ef83f88da836d4d6f Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 9 Feb 2021 15:24:12 +0100 Subject: [PATCH 062/124] Remove duplicate owner. Print current value for filter only if skip() is True (free Bet class from logger). Print only the stramers with updated. Close #64 --- .../TwitchChannelPointsMiner.py | 13 +++++++------ TwitchChannelPointsMiner/classes/Twitch.py | 7 ++++--- .../classes/entities/Bet.py | 18 ++++++------------ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index b85e960..a4389cc 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -295,12 +295,13 @@ class TwitchChannelPointsMiner: print("") for streamer_index in range(0, len(self.streamers)): - logger.info( - f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(self.streamers[streamer_index].channel_points - self.original_streamers[streamer_index].channel_points)}", - extra={"emoji": ":robot:"}, - ) if self.streamers[streamer_index].history != {}: logger.info( - f"{self.streamers[streamer_index].print_history()}", - extra={"emoji": ":moneybag:"}, + f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(self.streamers[streamer_index].channel_points - self.original_streamers[streamer_index].channel_points)}", + 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/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 8db5d4a..5f065d6 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -203,16 +203,17 @@ class Twitch(object): selector_index = 0 if decision["choice"] == "A" else 1 logger.info( - f"Going to complete bet for {event} owned by {event.streamer}", + f"Going to complete bet for {event}", extra={"emoji": ":four_leaf_clover:"}, ) if event.status == "ACTIVE": - if event.bet.skip() is True: + skip, compared_value = event.bet.skip() + if skip is True: logger.info( f"Skip betting for the event {event}", extra={"emoji": ":pushpin:"} ) logger.info( - f"Skip settings {event.bet.settings.filter_condition}", + f"Skip settings {event.bet.settings.filter_condition}, current value is: {compared_value}", extra={"emoji": ":pushpin:"}, ) else: diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 6070e7a..eaab53c 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -1,5 +1,4 @@ import copy -import logging from enum import Enum, auto from random import uniform @@ -7,8 +6,6 @@ from millify import millify from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round -logger = logging.getLogger(__name__) - class Strategy(Enum): MOST_VOTED = auto() @@ -193,25 +190,22 @@ class Bet(object): outcome_index = char_decision_as_index(self.decision["choice"]) compared_value = self.outcomes[outcome_index][fixed_key] - logger.info( - f"Filter applied on this bet. Current {key.upper()} is {compared_value}, must be {condition} {value}" - ) # Check if condition is satisfied if condition == Condition.GT: if compared_value > value: - return False + return False, compared_value elif condition == Condition.LT: if compared_value < value: - return False + return False, compared_value elif condition == Condition.GTE: if compared_value >= value: - return False + return False, compared_value elif condition == Condition.LTE: if compared_value <= value: - return False - return True # Else skip the bet + return False, compared_value + return True, compared_value # Else skip the bet else: - return False # Default don't skip the bet + return False, 0 # Default don't skip the bet def calculate(self, balance: int) -> dict: self.decision = {"choice": None, "amount": 0, "id": None} From 599a2c3074a5d1f30f2b6de893aeb155c0f195ea Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 10 Feb 2021 15:06:06 +0100 Subject: [PATCH 063/124] Suppress chardet.charsetprober --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index a4389cc..01869d3 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -28,6 +28,11 @@ from TwitchChannelPointsMiner.utils import ( set_default_settings, ) +# Suppress: +# - chardet.charsetprober - [feed] +# - chardet.charsetprober - [get_confidence] +logging.getLogger("chardet.charsetprober").setLevel(logging.ERROR) + logger = logging.getLogger(__name__) From ca3c5037afb5025a8093bd0105897fc524057c35 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 10 Feb 2021 15:17:02 +0100 Subject: [PATCH 064/124] Prevent multiple subscription to the same topics from multiple ws. https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/54\#issuecomment-776696545 --- .../classes/WebSocketsPool.py | 20 +++++++++++++++---- TwitchChannelPointsMiner/utils.py | 8 ++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 5bfa7d4..3aa606b 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -14,6 +14,7 @@ from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import ( _millify, + currently_connected_topics, get_streamer_index, internet_connection_available, ) @@ -40,12 +41,17 @@ class WebSocketsPool: if self.ws == [] or self.ws[-1] is None or len(self.ws[-1].topics) >= 50: self.append_new_websocket() - self.ws[-1].topics.append(topic) - if self.ws[-1].is_opened is False: self.ws[-1].pending_topics.append(topic) else: - self.ws[-1].listen(topic, self.twitch.twitch_login.get_auth_token()) + if topic not in currently_connected_topics(self.ws): + self.ws[-1].listen(topic, self.twitch.twitch_login.get_auth_token()) + else: + logger.warning( + f"#{self.ws[-1].index} - Another WebSocket It's currently connected to: {topic}" + ) + + self.ws[-1].topics.append(topic) def append_new_websocket(self): self.ws.append( @@ -78,7 +84,13 @@ class WebSocketsPool: ws.is_opened = True ws.ping() for topic in ws.pending_topics: - ws.listen(topic, ws.twitch.twitch_login.get_auth_token()) + # I know: ws.parent_pool.ws it's really strange + if topic not in currently_connected_topics(ws.parent_pool.ws): + ws.listen(topic, ws.twitch.twitch_login.get_auth_token()) + else: + logger.warning( + f"#{ws.index} - Another WebSocket It's currently connected to: {topic}" + ) while not ws.is_closed: ws.ping() diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index da8de53..992ea58 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -140,3 +140,11 @@ def internet_connection_available(host="8.8.8.8", port=53, timeout=3): return True except socket.error: return False + + +def currently_connected_topics(ws_pool): + merged_topics = [] + for ws in ws_pool: + if ws is not None: + merged_topics += ws.topics + return merged_topics From 137d5d5e3451b0ad274e01c04e6505956bf97dab Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 11 Feb 2021 10:42:14 +0100 Subject: [PATCH 065/124] I don't know why but I've already done this commit yesterday but It was lost :confused: --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 87e5aa9..884ffc3 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -50,8 +50,7 @@ class WebSocketsPool: logger.warning( f"#{self.ws[-1].index} - Another WebSocket It's currently connected to: {topic}" ) - - self.ws[-1].topics.append(topic) + self.ws[-1].topics.append(topic) def append_new_websocket(self): self.ws.append( @@ -91,6 +90,7 @@ class WebSocketsPool: logger.warning( f"#{ws.index} - Another WebSocket It's currently connected to: {topic}" ) + ws.topics.append(topic) while not ws.is_closed: ws.ping() From 7c2dd5395172fb9131e71e577f686bde0623dc6f Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 11 Feb 2021 16:58:48 +0100 Subject: [PATCH 066/124] Appen new ws also when the pending_topics are gte 50 --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 884ffc3..d4a92c6 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -38,7 +38,12 @@ class WebSocketsPool: def submit(self, topic): # Check if we need to create a new WebSocket instance - if self.ws == [] or self.ws[-1] is None or len(self.ws[-1].topics) >= 50: + if ( + self.ws == [] + or self.ws[-1] is None + or len(self.ws[-1].topics) >= 50 + or len(self.ws[-1].pending_topics) >= 50 + ): self.append_new_websocket() if self.ws[-1].is_opened is False: From 2c843bc2527a326fecb8141b3c7c891e40d721be Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 11 Feb 2021 21:35:26 +0100 Subject: [PATCH 067/124] Start with drops improvement. Currently we try to handle the inventory and dashboard for correct percentage logs. Save in stream Object also the drops drops_available. This commit and the future are all related to #65 #40 #35 #27 --- .../TwitchChannelPointsMiner.py | 8 ++ TwitchChannelPointsMiner/classes/Twitch.py | 122 +++++++++++++++++- .../classes/WebSocketsPool.py | 5 +- .../classes/entities/Stream.py | 7 +- TwitchChannelPointsMiner/constants.py | 19 +++ 5 files changed, 157 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 01869d3..9941bb6 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -179,6 +179,12 @@ class TwitchChannelPointsMiner: ) self.minute_watcher_thread.start() + self.sync_drops_campaigns_thread = threading.Thread( + target=self.twitch.sync_drops_campaigns, + args=(self.streamers,), + ) + self.sync_drops_campaigns_thread.start() + self.ws_pool = WebSocketsPool( twitch=self.twitch, streamers=self.streamers, @@ -193,6 +199,7 @@ class TwitchChannelPointsMiner: ) ) + """ # If we have at least one streamer with settings = claim_drops True # Going to subscribe to user-drop-events. Get update for drop-progress claim_drops = at_least_one_value_in_settings_is( @@ -205,6 +212,7 @@ class TwitchChannelPointsMiner: user_id=self.twitch.twitch_login.get_user_id(), ) ) + """ # Going to subscribe to predictions-user-v1. Get update when we place a new prediction (confirm) if make_predictions is True: diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 5f065d6..cb2aa04 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -10,6 +10,7 @@ import os import random import re import time +from datetime import datetime from pathlib import Path from secrets import token_hex @@ -177,12 +178,131 @@ class Twitch(object): for drop in campaign["timeBasedDrops"]: if drop["self"]["dropInstanceID"] is not None: self.claim_drop(drop["self"]["dropInstanceID"]) - time.sleep(random.uniform(10, 30)) + time.sleep(random.uniform(5, 10)) def __get_inventory(self): response = self.post_gql_request(GQLOperations.Inventory) return response["data"]["currentUser"]["inventory"] + def __get_drops_dashboard(self, status=None): + response = self.post_gql_request(GQLOperations.ViewerDropsDashboard) + campaigns = response["data"]["currentUser"]["dropCampaigns"] + if status is not None: + campaigns = [camp for camp in campaigns if camp["status"] == status.upper()] + return campaigns + + # I'm not sure that this method It's fully working. We don't need it for the moment + def __get_campaigns_details(self, campaigns): + json_data = [] + for campaign in campaigns: + 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) + return [res["data"]["user"]["dropCampaign"] for res in response] + + def sync_drops_campaigns(self, streamers): + while self.running: + campaigns_details = self.__get_campaigns_details( + self.__get_drops_dashboard(status="ACTIVE") + ) + + # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet + current_dt = datetime.now() + for index in range(0, len(campaigns_details)): + drops = campaigns_details[index]["timeBasedDrops"] + campaigns_details[index]["timeBasedDrops"] = [ + drop + for drop in drops + if datetime.strptime(drop["startAt"], "%Y-%m-%dT%H:%M:%SZ") + < current_dt + < datetime.strptime(drop["endAt"], "%Y-%m-%dT%H:%M:%SZ") + ] + print( + campaigns_details[index]["id"], + len(drops), + len(campaigns_details[index]["timeBasedDrops"]), + ) + + campaigns_details = [ + camp for camp in campaigns_details if camp["timeBasedDrops"] != [] + ] + + # Check if user It's currently streaming the same game present in campaigns_details + for index in range(0, len(streamers)): + if ( + streamers[index].settings.claim_drops is True + and streamers[index].is_online is True + and streamers[index].stream.drops_tags is True + ): + streamers[index].stream.drops_campaigns = [] + # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! + for campaign in campaigns_details: + if campaign["game"] == streamers[index].stream.game: + streamers[index].stream.drops_campaigns.append(campaign) + + """ + drops_available = sum( + [ + len(camp["timeBasedDrops"]) + for camp in streamers[index].stream.drops_campaigns + ] + ) + logger.info( + f"{streamers[index]} : {streamers[index].stream} - Active campaigns {len(streamers[index].stream.drops_campaigns)} - Drops available: {drops_available}" + ) + """ + + active_campaigns_id = [camp["id"] for camp in campaigns_details] + + inventory = self.__get_inventory() + print("\n") + for campaign in inventory["dropCampaignsInProgress"]: + # We are currently take part of this campaign. + if campaign["id"] in active_campaigns_id: + active_campaigns_id.remove(campaign["id"]) + + for drop in campaign["timeBasedDrops"]: + percentage = int( + ( + drop["self"]["currentMinutesWatched"] + / drop["requiredMinutesWatched"] + ) + * 100 + ) + benefit = [bf["benefit"]["name"] for bf in drop["benefitEdges"]] + logger.info( + f"Campaign {campaign['name']} ({campaign['id']}) - Game: {campaign['game']['name']}" + ) + logger.info(f"Drop: {drop['name']} ({drop['id']})") + + end_at = datetime.strptime(drop["endAt"], "%Y-%m-%dT%H:%M:%SZ") + start_at = datetime.strptime(drop["startAt"], "%Y-%m-%dT%H:%M:%SZ") + logger.info(f"endAt: {end_at}, startAt: {start_at}") + if datetime.now() > end_at: + logger.info("The event it's over.") + elif datetime.now() < start_at: + logger.info("The event it's not started") + else: + logger.info("You should able to collect this drop") + + logger.info( + f"PreconditionMet: {drop['self']['hasPreconditionsMet']}, Required: {drop['requiredMinutesWatched']}, Watched: {drop['self']['currentMinutesWatched']}, Percentage: {percentage}%" + ) + logger.info(f"Benefit: {', '.join(benefit)}") + print("\n") + + logger.info(f"We could start all of this campaigns: {active_campaigns_id}") + logger.info( + f"Campaign active: {len(active_campaigns_id)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" + ) + + # time.sleep(30) # Debugging time for the moment + exit(1) + # 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) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index e49b3c2..b3d8dc6 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -9,7 +9,8 @@ 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.Exceptions import TimeBasedDropNotFound + +# from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import _millify, get_streamer_index @@ -302,6 +303,7 @@ class WebSocketsPool: elif message.type == "prediction-made": event_prediction.bet_confirmed = True + """ elif message.topic == "user-drop-events": if message.type == "drop-progress": current = message.data["current_progress_min"] @@ -329,6 +331,7 @@ class WebSocketsPool: f"Drop event {percentage_state}% for {ws.streamers[streamer_index]}!", extra={"emoji": ":package:"}, ) + """ except Exception: logger.error( diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index affce68..95a63d5 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -16,7 +16,10 @@ class Stream(object): self.title = None self.game = {} self.tags = [] - self.drops_enabled = False + + self.drops_tags = False + self.drops_campaigns = [] + self.viewers_count = 0 self.__last_update = 0 @@ -36,7 +39,7 @@ class Stream(object): self.tags = tags self.viewers_count = viewers_count - self.drops_enabled = ( + self.drops_tags = ( DROP_ID in [tag["id"] for tag in self.tags] and self.game != {} ) self.__last_update = time.time() diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 4969f08..1ff05a7 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -101,3 +101,22 @@ class GQLOperations: } }, } + ViewerDropsDashboard = { + "operationName": "ViewerDropsDashboard", + "variables": {}, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "c4d61d7b71d03b324914d3cf8ca0bc23fe25dacf54120cc954321b9704a3f4e2", + } + }, + } + DropCampaignDetails = { + "operationName": "DropCampaignDetails", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "7da6078b1bfa2f0a4dd061cb47bdcd1ffddf31cccadd966ec192e4cd06666e2b", + } + }, + } From 8c6212deb18fbd6fd5dbf3f599085cbcd5f41a90 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 11 Feb 2021 22:28:50 +0100 Subject: [PATCH 068/124] Create a class for Drop and Campaign. Create a clear structure to understand (I hope) --- TwitchChannelPointsMiner/classes/Twitch.py | 114 ++++++------------ .../classes/entities/Campaign.py | 30 +++++ .../classes/entities/Drop.py | 45 +++++++ 3 files changed, 111 insertions(+), 78 deletions(-) create mode 100644 TwitchChannelPointsMiner/classes/entities/Campaign.py create mode 100644 TwitchChannelPointsMiner/classes/entities/Drop.py diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index cb2aa04..9b0c4c6 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -10,12 +10,12 @@ import os import random import re import time -from datetime import datetime from pathlib import Path from secrets import token_hex import requests +from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign from TwitchChannelPointsMiner.classes.Exceptions import ( StreamerDoesNotExistException, StreamerIsOfflineException, @@ -206,30 +206,44 @@ class Twitch(object): def sync_drops_campaigns(self, streamers): while self.running: - campaigns_details = self.__get_campaigns_details( - self.__get_drops_dashboard(status="ACTIVE") - ) + campaigns_active = self.__get_drops_dashboard(status="ACTIVE") + campaigns_details = self.__get_campaigns_details(campaigns_active) + campaigns_available = [] # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet - current_dt = datetime.now() for index in range(0, len(campaigns_details)): - drops = campaigns_details[index]["timeBasedDrops"] - campaigns_details[index]["timeBasedDrops"] = [ - drop - for drop in drops - if datetime.strptime(drop["startAt"], "%Y-%m-%dT%H:%M:%SZ") - < current_dt - < datetime.strptime(drop["endAt"], "%Y-%m-%dT%H:%M:%SZ") - ] - print( - campaigns_details[index]["id"], - len(drops), - len(campaigns_details[index]["timeBasedDrops"]), - ) + campaign = Campaign(campaigns_details[index]) + if campaign.dt_match is True: + campaign.clear_drops() + if campaign.drops != []: + campaigns_available.append(campaign) - campaigns_details = [ - camp for camp in campaigns_details if camp["timeBasedDrops"] != [] + inventory = self.__get_inventory() + for i in range(0, len(campaigns_available)): + for campaign_in_progress in inventory["dropCampaignsInProgress"]: + if campaign_in_progress["id"] == campaigns_available[i].id: + campaigns_available[i].in_inventory = True + logger.info(campaigns_available[i]) + for drop_in_progress in campaign_in_progress["timeBasedDrops"]: + for j in range(0, len(campaigns_available[i].drops)): + current_id = campaigns_available[i].drops[j].id + if drop_in_progress["id"] == current_id: + campaigns_available[i].drops[j].update( + drop_in_progress["self"] + ) + logger.info(campaigns_available[i].drops[j]) + break + break + + campaign_not_started = [ + camp for camp in campaigns_available if camp.in_inventory is False ] + logger.info( + f"We could start all of this campaigns: {len(campaign_not_started)}" + ) + logger.info( + f"Campaign active in dashboard: {len(campaigns_available)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" + ) # Check if user It's currently streaming the same game present in campaigns_details for index in range(0, len(streamers)): @@ -240,66 +254,10 @@ class Twitch(object): ): streamers[index].stream.drops_campaigns = [] # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - for campaign in campaigns_details: - if campaign["game"] == streamers[index].stream.game: + for campaign in campaigns_available: + if campaign.game == streamers[index].stream.game: streamers[index].stream.drops_campaigns.append(campaign) - """ - drops_available = sum( - [ - len(camp["timeBasedDrops"]) - for camp in streamers[index].stream.drops_campaigns - ] - ) - logger.info( - f"{streamers[index]} : {streamers[index].stream} - Active campaigns {len(streamers[index].stream.drops_campaigns)} - Drops available: {drops_available}" - ) - """ - - active_campaigns_id = [camp["id"] for camp in campaigns_details] - - inventory = self.__get_inventory() - print("\n") - for campaign in inventory["dropCampaignsInProgress"]: - # We are currently take part of this campaign. - if campaign["id"] in active_campaigns_id: - active_campaigns_id.remove(campaign["id"]) - - for drop in campaign["timeBasedDrops"]: - percentage = int( - ( - drop["self"]["currentMinutesWatched"] - / drop["requiredMinutesWatched"] - ) - * 100 - ) - benefit = [bf["benefit"]["name"] for bf in drop["benefitEdges"]] - logger.info( - f"Campaign {campaign['name']} ({campaign['id']}) - Game: {campaign['game']['name']}" - ) - logger.info(f"Drop: {drop['name']} ({drop['id']})") - - end_at = datetime.strptime(drop["endAt"], "%Y-%m-%dT%H:%M:%SZ") - start_at = datetime.strptime(drop["startAt"], "%Y-%m-%dT%H:%M:%SZ") - logger.info(f"endAt: {end_at}, startAt: {start_at}") - if datetime.now() > end_at: - logger.info("The event it's over.") - elif datetime.now() < start_at: - logger.info("The event it's not started") - else: - logger.info("You should able to collect this drop") - - logger.info( - f"PreconditionMet: {drop['self']['hasPreconditionsMet']}, Required: {drop['requiredMinutesWatched']}, Watched: {drop['self']['currentMinutesWatched']}, Percentage: {percentage}%" - ) - logger.info(f"Benefit: {', '.join(benefit)}") - print("\n") - - logger.info(f"We could start all of this campaigns: {active_campaigns_id}") - logger.info( - f"Campaign active: {len(active_campaigns_id)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" - ) - # time.sleep(30) # Debugging time for the moment exit(1) diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py new file mode 100644 index 0000000..885b1cb --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -0,0 +1,30 @@ +from datetime import datetime + +from TwitchChannelPointsMiner.classes.entities.Drop import Drop + + +class Campaign(object): + def __init__(self, dict): + self.id = dict["id"] + self.game = dict["game"] + self.name = dict["name"] + self.status = dict["status"] + 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 = [Drop(drop) for drop in dict["timeBasedDrops"]] + + def __repr__(self): + return f"Campaign(id={self.id}, name={self.name}, game={self.game}, in_inventory={self.in_inventory})" + + def clear_drops(self): + self.drops = [drop for drop in self.drops if drop.dt_match is True] + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.id == other.id + else: + return False diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py new file mode 100644 index 0000000..36981dc --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -0,0 +1,45 @@ +from datetime import datetime + + +class Drop(object): + 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 = False + self.current_minutes_watched = 0 + self.drop_instance_id = None + self.is_claimed = 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 + + def update( + self, + progress, + ): + self.has_preconditions_met = progress["hasPreconditionsMet"] + self.current_minutes_watched = progress["currentMinutesWatched"] + self.drop_instance_id = progress["dropInstanceID"] + self.is_claimed = progress["isClaimed"] + self.percentage_progress = ( + 0 + if self.current_minutes_watched == 0 + else int( + (self.current_minutes_watched / self.current_minutes_watched) * 100 + ) + ) + + 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}, drop_instance_id={self.drop_instance_id}, is_claimed={self.is_claimed})" + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.id == other.id + else: + return False From 175d6abe126dfba8b1942e4787f449c21d724f0e Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 00:15:47 +0100 Subject: [PATCH 069/124] Instead of appending every time a new ws at the end of array replace at same index. Seems to be a good solution. --- .../TwitchChannelPointsMiner.py | 6 +- TwitchChannelPointsMiner/classes/Twitch.py | 72 +++++++------- .../classes/WebSocketsPool.py | 96 +++++++++---------- TwitchChannelPointsMiner/utils.py | 8 -- 4 files changed, 85 insertions(+), 97 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index d1e8710..e6c8560 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -236,12 +236,12 @@ class TwitchChannelPointsMiner: # 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 not None - and self.ws_pool.ws[index].elapsed_last_ping() > 15 + self.ws_pool.ws[index].is_reconneting 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 15 minutes ago. Reconnecting to the WebSocket..." + f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..." ) WebSocketsPool.handle_reconnection(self.ws_pool.ws[index]) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2034c59..347ea82 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -48,30 +48,31 @@ class Twitch(object): def update_stream(self, streamer): if streamer.stream.update_required() is True: stream_info = self.get_stream_info(streamer) - 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"], - ) + 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(), - } + event_properties = { + "channel_id": streamer.channel_id, + "broadcast_id": streamer.stream.broadcast_id, + "player": "site", + "user_id": self.twitch_login.get_user_id(), + } - if ( - streamer.stream.game_name() is not None - and streamer.settings.claim_drops is True - ): - event_properties["game"] = streamer.stream.game_name() + if ( + streamer.stream.game_name() is not None + and streamer.settings.claim_drops is True + ): + event_properties["game"] = streamer.stream.game_name() - streamer.stream.payload = [ - {"event": "minute-watched", "properties": event_properties} - ] + streamer.stream.payload = [ + {"event": "minute-watched", "properties": event_properties} + ] def get_spade_url(self, streamer): try: @@ -231,21 +232,22 @@ class Twitch(object): extra={"emoji": ":pushpin:"}, ) else: - logger.info( - f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", - extra={"emoji": ":four_leaf_clover:"}, - ) + if decision["amount"] > 0: + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) - json_data = copy.deepcopy(GQLOperations.MakePrediction) - json_data["variables"] = { - "input": { - "eventID": event.event_id, - "outcomeID": decision["id"], - "points": decision["amount"], - "transactionID": token_hex(16), + json_data = copy.deepcopy(GQLOperations.MakePrediction) + json_data["variables"] = { + "input": { + "eventID": event.event_id, + "outcomeID": decision["id"], + "points": decision["amount"], + "transactionID": token_hex(16), + } } - } - return self.post_gql_request(json_data) + return self.post_gql_request(json_data) else: logger.info( f"Oh no! The event is not active anymore! Current status: {event.status}", @@ -330,7 +332,7 @@ class Twitch(object): if response.status_code == 204: streamers[index].stream.update_minute_watched() except requests.exceptions.RequestException as e: - logger.error(f"Error while trying to watch a minute: {e}") + logger.error(f"Error while trying to send minute watched: {e}") # The success rate It's very hight usually. Why we have failed? # Check internet connection ... diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index d4a92c6..7d1193d 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -14,7 +14,6 @@ from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import ( _millify, - currently_connected_topics, get_streamer_index, internet_connection_available, ) @@ -38,49 +37,44 @@ class WebSocketsPool: def submit(self, topic): # Check if we need to create a new WebSocket instance - if ( - self.ws == [] - or self.ws[-1] is None - or len(self.ws[-1].topics) >= 50 - or len(self.ws[-1].pending_topics) >= 50 - ): - self.append_new_websocket() + if self.ws == [] or len(self.ws[-1].topics) >= 50: + self.ws.append(self.__new(len(self.ws))) + self.__start(-1) - if self.ws[-1].is_opened is False: - self.ws[-1].pending_topics.append(topic) + 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: - if topic not in currently_connected_topics(self.ws): - self.ws[-1].listen(topic, self.twitch.twitch_login.get_auth_token()) - else: - logger.warning( - f"#{self.ws[-1].index} - Another WebSocket It's currently connected to: {topic}" - ) - self.ws[-1].topics.append(topic) + self.ws[index].listen(topic, self.twitch.twitch_login.get_auth_token()) - def append_new_websocket(self): - self.ws.append( - TwitchWebSocket( - index=len(self.ws), - 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 __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. ) - self.thread_ws = threading.Thread(target=lambda: self.ws[-1].run_forever()) - self.thread_ws.daemon = True - self.thread_ws.name = f"WebSocket #{self.ws[-1].index}" - self.thread_ws.start() + def __start(self, index): + thread_ws = threading.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)): - if self.ws[index] is not None: - self.ws[index].forced_close = True - self.ws[index].close() + self.ws[index].forced_close = True + self.ws[index].close() @staticmethod def on_open(ws): @@ -88,22 +82,15 @@ class WebSocketsPool: ws.is_opened = True ws.ping() for topic in ws.pending_topics: - # I know: ws.parent_pool.ws it's really strange - if topic not in currently_connected_topics(ws.parent_pool.ws): - ws.listen(topic, ws.twitch.twitch_login.get_auth_token()) - else: - logger.warning( - f"#{ws.index} - Another WebSocket It's currently connected to: {topic}" - ) - ws.topics.append(topic) + ws.listen(topic, ws.twitch.twitch_login.get_auth_token()) - while not ws.is_closed: + while ws.is_closed is False: ws.ping() time.sleep(random.uniform(25, 30)) - if ws.elapsed_last_pong() > 10 and ws.is_reconneting is False: + if ws.elapsed_last_pong() > 5 and ws.is_reconneting is False: logger.info( - f"#{ws.index} - The last PONG was received more than 10 minutes ago" + f"#{ws.index} - The last PONG was received more than 5 minutes ago" ) ws.is_reconneting = True WebSocketsPool.handle_reconnection(ws) @@ -114,6 +101,8 @@ class WebSocketsPool: @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 @@ -125,13 +114,12 @@ class WebSocketsPool: @staticmethod def handle_reconnection(ws): # Close the current WebSocket. - # anyway, we replace the ws with None ws.is_closed = True ws.keep_running = False # Reconnect only if ws.forced_close is False (replace the keep_running) if ws.forced_close is False: logger.info( - f"#{ws.index} - Reconnecting to Twitch PubSub server in 30 seconds" + f"#{ws.index} - Reconnecting to Twitch PubSub server in ~60 seconds" ) time.sleep(30) @@ -142,10 +130,16 @@ class WebSocketsPool: ) time.sleep(random_sleep * 60) + # Why not create a new ws on the same array index? Let's try. self = ws.parent_pool - self.ws[ws.index] = None + self.ws[ws.index] = self.__new(ws.index) # Create a new connection. + # self.ws[ws.index].topics = ws.topics + + self.__start(ws.index) # Start a new thread. + time.sleep(30) + for topic in ws.topics: - self.submit(topic) + self.__submit(ws.index, topic) @staticmethod def on_message(ws, message): diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 992ea58..da8de53 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -140,11 +140,3 @@ def internet_connection_available(host="8.8.8.8", port=53, timeout=3): return True except socket.error: return False - - -def currently_connected_topics(ws_pool): - merged_topics = [] - for ws in ws_pool: - if ws is not None: - merged_topics += ws.topics - return merged_topics From a483c982d9cc8f852748c6924be9f6513813b423 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 11:06:19 +0100 Subject: [PATCH 070/124] Sync the dashboard each 30m. Create Enum for manager the priority of watching. You can watch upon a order of streamers inserted, do priority to watch-streak or collect drops as your first priority --- .../TwitchChannelPointsMiner.py | 12 +- TwitchChannelPointsMiner/classes/Settings.py | 9 + TwitchChannelPointsMiner/classes/Twitch.py | 186 +++++++++++------- .../classes/entities/Campaign.py | 6 +- .../classes/entities/Drop.py | 4 + 5 files changed, 136 insertions(+), 81 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 9941bb6..37cc553 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -17,7 +17,7 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import ( StreamerSettings, ) from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException -from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.classes.Settings import Priority, Settings from TwitchChannelPointsMiner.classes.Twitch import Twitch from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers @@ -41,6 +41,7 @@ class TwitchChannelPointsMiner: self, username: str, claim_drops_startup: bool = False, + priority=[Priority.ORDER, Priority.STREAK, Priority.DROPS], # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), # Default values for all streamers @@ -60,6 +61,8 @@ class TwitchChannelPointsMiner: self.twitch = Twitch(self.username, user_agent) self.claim_drops_startup = claim_drops_startup + self.priority = priority + self.streamers = [] self.events_predictions = {} self.minute_watcher_thread = None @@ -170,12 +173,7 @@ class TwitchChannelPointsMiner: self.minute_watcher_thread = threading.Thread( target=self.twitch.send_minute_watched_events, - args=( - self.streamers, - at_least_one_value_in_settings_is( - self.streamers, "watch_streak", True - ), - ), + args=(self.streamers, self.priority), ) self.minute_watcher_thread.start() diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index 8794e78..85af5bd 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -1,3 +1,12 @@ +from enum import Enum, auto + + +class Priority(Enum): + ORDER = auto() + STREAK = auto() + DROPS = auto() + + # Empty object shared between class class Settings(object): pass diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 9b0c4c6..ead0006 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -21,7 +21,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import ( StreamerIsOfflineException, TimeBasedDropNotFound, ) -from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.classes.Settings import Priority, Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin from TwitchChannelPointsMiner.constants import API, CLIENT_ID, GQLOperations from TwitchChannelPointsMiner.utils import _millify @@ -150,18 +150,11 @@ class Twitch(object): } self.post_gql_request(json_data) - def claim_drop(self, drop_instance_id, streamer=None): - if streamer is not None: - logger.info( - f"Claiming the drop for {streamer}!", extra={"emoji": ":package:"} - ) - else: - logger.info( - f"Startup claim drop {drop_instance_id}", extra={"emoji": ":package:"} - ) + def claim_drop(self, drop): + logger.info(f"Claim {drop}", extra={"emoji": ":package:"}) json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) - json_data["variables"] = {"input": {"dropInstanceID": drop_instance_id}} + json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} self.post_gql_request(json_data) def search_drop_in_inventory(self, streamer, drop_id): @@ -205,45 +198,64 @@ class Twitch(object): return [res["data"]["user"]["dropCampaign"] for res in response] def sync_drops_campaigns(self, streamers): + campaigns_update = 0 while self.running: - campaigns_active = self.__get_drops_dashboard(status="ACTIVE") - campaigns_details = self.__get_campaigns_details(campaigns_active) - campaigns_available = [] + # Get update from dashboard each 30minutes + if campaigns_update == 0 or ((time.time() - campaigns_update) / 60) > 30: + 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)): - campaign = Campaign(campaigns_details[index]) - if campaign.dt_match is True: - campaign.clear_drops() - if campaign.drops != []: - campaigns_available.append(campaign) + # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet + for index in range(0, len(campaigns_details)): + 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) + # Get data from inventory and sync current status with streamers.drops_campaigns inventory = self.__get_inventory() - for i in range(0, len(campaigns_available)): - for campaign_in_progress in inventory["dropCampaignsInProgress"]: - if campaign_in_progress["id"] == campaigns_available[i].id: - campaigns_available[i].in_inventory = True - logger.info(campaigns_available[i]) - for drop_in_progress in campaign_in_progress["timeBasedDrops"]: - for j in range(0, len(campaigns_available[i].drops)): - current_id = campaigns_available[i].drops[j].id - if drop_in_progress["id"] == current_id: - campaigns_available[i].drops[j].update( - drop_in_progress["self"] - ) - logger.info(campaigns_available[i].drops[j]) - break - break + # 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(0, len(campaigns)): + # Iterate all campaigns currently in progress from out inventory + for in_progress in inventory["dropCampaignsInProgress"]: + if in_progress["id"] == campaigns[i].id: + campaigns[i].in_inventory = True + logger.info(campaigns[i]) + # Iterate all the drops from inventory + for drop in in_progress["timeBasedDrops"]: + # Iterate all the drops from out campaigns array + # After id match update with + # - currentMinutesWatched + # - hasPreconditionsMet + # - dropInstanceID + # - isClaimed + for j in range(0, len(campaigns[i].drops)): + current_id = campaigns[i].drops[j].id + if drop["id"] == current_id: + campaigns[i].drops[j].update(drop["self"]) + # If after update we all conditions are meet we can claim the drop + if campaigns[i].drops[j].is_claimable is True: + self.claim_drop(campaigns[i].drops[j]) + logger.info(campaigns[i].drops[j]) + break # Found it! + break # Found it! + """ campaign_not_started = [ - camp for camp in campaigns_available if camp.in_inventory is False + campaign for campaign in campaigns if campaign.in_inventory is False ] logger.info( f"We could start all of this campaigns: {len(campaign_not_started)}" ) logger.info( - f"Campaign active in dashboard: {len(campaigns_available)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" + f"Campaign active in dashboard: {len(campaigns)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" ) + """ # Check if user It's currently streaming the same game present in campaigns_details for index in range(0, len(streamers)): @@ -254,12 +266,11 @@ class Twitch(object): ): streamers[index].stream.drops_campaigns = [] # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - for campaign in campaigns_available: + for campaign in campaigns: if campaign.game == streamers[index].stream.game: streamers[index].stream.drops_campaigns.append(campaign) - # time.sleep(30) # Debugging time for the moment - exit(1) + time.sleep(60) # Load the amount of current points for a channel, check if a bonus is available def load_channel_points_context(self, streamer): @@ -316,8 +327,13 @@ class Twitch(object): extra={"emoji": ":disappointed_relieved:"}, ) - def send_minute_watched_events(self, streamers, watch_streak=False, chunk_size=3): + def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: + # OK! We will do the following: + # - Create an array of int - index of streamers currently online + # - Create a dictionary with grouped streamers, based on watch-streak or drops + # - For each array we don't need more than 2 streamer (becuase we can't watch more than 2) + streamers_index = [ i for i in range(0, len(streamers)) @@ -328,39 +344,63 @@ class Twitch(object): ) ] - """ - 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 - """ streamers_watching = [] - if watch_streak is True: - 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 - ): - logger.debug( - f"Switch priority: {streamers[index]}, WatchStreak missing is {streamers[index].stream.watch_streak_missing} and minute_watched: {round(streamers[index].stream.minute_watched, 2)}" - ) - streamers_watching.append(index) - if len(streamers_watching) == 2: - break + 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.append(streamers_index[: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. - if streamers_watching == []: - streamers_watching = streamers_index - else: - while len(streamers_watching) < 2 and len(streamers_index) > 1: - another_streamer_index = streamers_index.pop(0) - if another_streamer_index not in streamers_watching: - streamers_watching.append(another_streamer_index) + 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 + ): + logger.debug( + f"Switch priority: {streamers[index]}, WatchStreak missing is {streamers[index].stream.watch_streak_missing} and minute_watched: {round(streamers[index].stream.minute_watched, 2)}" + ) + streamers_watching.append(index) + if len(streamers_watching) == 2: + break + + elif prior == Priority.DROPS and len(streamers_watching) <= 2: + for index in streamers_index: + # For the truth we don't need al of this If - condition + # because the drops_campaigns can be fulled only if claim_drops is True and drops_tags is True + if ( + streamers[index].settings.claim_drops is True + and streamers[index].stream.drops_tags is True + and streamers[index].stream.drops_campaigns != [] + ): + drops_available = sum( + [ + len(campaign.drops) + for campaign in streamers[ + index + ].stream.drops_campaigns + ] + ) + logger.debug( + f"{streamers[index]} it's currenty stream: {streamers[index].stream}" + ) + logger.debug( + f"Campaign currently active here: {len(streamers[index].stream.drops_campaigns)}, drops available: {drops_available}" + ) + streamers_watching.append(index) + if len(streamers_watching) == 2: + break """ Twitch has a limit - you can't watch more than 2 channels at one time. diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index 885b1cb..e79a4b8 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -21,7 +21,11 @@ class Campaign(object): return f"Campaign(id={self.id}, name={self.name}, game={self.game}, in_inventory={self.in_inventory})" def clear_drops(self): - self.drops = [drop for drop in self.drops if drop.dt_match is True] + self.drops = [ + drop + for drop in self.drops + if drop.dt_match is True and drop.is_claimed is False + ] def __eq__(self, other): if isinstance(other, self.__class__): diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py index 36981dc..a1d6cee 100644 --- a/TwitchChannelPointsMiner/classes/entities/Drop.py +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -14,6 +14,7 @@ class Drop(object): self.current_minutes_watched = 0 self.drop_instance_id = None self.is_claimed = False + self.is_claimable = 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") @@ -34,6 +35,9 @@ class Drop(object): (self.current_minutes_watched / self.current_minutes_watched) * 100 ) ) + self.is_claimable = ( + self.is_claimed is False and self.drop_instance_id is not None + ) 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}, drop_instance_id={self.drop_instance_id}, is_claimed={self.is_claimed})" From 5336b88e5bd2e382a7fce5473562cd5248044c54 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 15:52:18 +0100 Subject: [PATCH 071/124] Print the updated status of current streamer - inventory - drops - campaign and all the info. For the moment we print each 1m but we should print on if: round((drop.percentage_progress / 25), 4).is_integer() - So at quarter: (0, 25, 50, 75, 100)% - Other fix and update, the priority system seems to work for now --- .../TwitchChannelPointsMiner.py | 16 ++-- TwitchChannelPointsMiner/classes/Twitch.py | 80 +++++++++++++------ .../classes/entities/Campaign.py | 8 ++ .../classes/entities/Drop.py | 25 +++++- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 37cc553..ceff172 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -41,7 +41,7 @@ class TwitchChannelPointsMiner: self, username: str, claim_drops_startup: bool = False, - priority=[Priority.ORDER, Priority.STREAK, Priority.DROPS], + priority=[Priority.DROPS, Priority.STREAK, Priority.ORDER], # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), # Default values for all streamers @@ -171,18 +171,19 @@ class TwitchChannelPointsMiner: self.streamers, "make_predictions", True ) + self.sync_drops_inventory_thread = threading.Thread( + target=self.twitch.sync_drops_inventory, + args=(self.streamers,), + ) + self.sync_drops_inventory_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.start() - self.sync_drops_campaigns_thread = threading.Thread( - target=self.twitch.sync_drops_campaigns, - args=(self.streamers,), - ) - self.sync_drops_campaigns_thread.start() - self.ws_pool = WebSocketsPool( twitch=self.twitch, streamers=self.streamers, @@ -255,6 +256,7 @@ class TwitchChannelPointsMiner: self.ws_pool.end() self.minute_watcher_thread.join() + self.sync_drops_inventory_thread.join() time.sleep(1) self.__print_report() diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index ead0006..c9f5a5f 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -184,7 +184,6 @@ class Twitch(object): campaigns = [camp for camp in campaigns if camp["status"] == status.upper()] return campaigns - # I'm not sure that this method It's fully working. We don't need it for the moment def __get_campaigns_details(self, campaigns): json_data = [] for campaign in campaigns: @@ -197,11 +196,12 @@ class Twitch(object): response = self.post_gql_request(json_data) return [res["data"]["user"]["dropCampaign"] for res in response] - def sync_drops_campaigns(self, streamers): + def sync_drops_inventory(self, streamers, chunk_size=3): campaigns_update = 0 while self.running: - # Get update from dashboard each 30minutes - if campaigns_update == 0 or ((time.time() - campaigns_update) / 60) > 30: + # Get update from dashboard each 60minutes + if campaigns_update == 0 or ((time.time() - campaigns_update) / 60) > 60: + # Get full details from current ACTIVE campaigns campaigns_details = self.__get_campaigns_details( self.__get_drops_dashboard(status="ACTIVE") ) @@ -225,7 +225,7 @@ class Twitch(object): for in_progress in inventory["dropCampaignsInProgress"]: if in_progress["id"] == campaigns[i].id: campaigns[i].in_inventory = True - logger.info(campaigns[i]) + # logger.info(campaigns[i]) # Iterate all the drops from inventory for drop in in_progress["timeBasedDrops"]: # Iterate all the drops from out campaigns array @@ -241,8 +241,12 @@ class Twitch(object): # If after update we all conditions are meet we can claim the drop if campaigns[i].drops[j].is_claimable is True: self.claim_drop(campaigns[i].drops[j]) - logger.info(campaigns[i].drops[j]) + # logger.info(campaigns[i].drops[j]) + # logger.info(campaigns[i].drops[j].progress_bar()) break # Found it! + # print("\n") + # Remove all the claime drops + campaigns[i].clear_drops() break # Found it! """ @@ -264,13 +268,23 @@ class Twitch(object): and streamers[index].is_online is True and streamers[index].stream.drops_tags is True ): - streamers[index].stream.drops_campaigns = [] # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - for campaign in campaigns: - if campaign.game == streamers[index].stream.game: - streamers[index].stream.drops_campaigns.append(campaign) + streamers[index].stream.drops_campaigns = [ + campaign + for campaign in campaigns + if campaign.drops != [] + and campaign.game == streamers[index].stream.game + ] - time.sleep(60) + self.__chuncked_sleep(60, chunk_size=chunk_size) + + # 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 # Load the amount of current points for a channel, check if a bonus is available def load_channel_points_context(self, streamer): @@ -337,7 +351,7 @@ class Twitch(object): streamers_index = [ i for i in range(0, len(streamers)) - if streamers[i].is_online + if streamers[i].is_online is True and ( streamers[i].online_at == 0 or (time.time() - streamers[i].online_at) > 30 @@ -346,10 +360,10 @@ class Twitch(object): streamers_watching = [] for prior in priority: - if prior == Priority.ORDER and len(streamers_watching) <= 2: + if prior == Priority.ORDER and len(streamers_watching) < 2: # Get the first 2 items, they are already in order - streamers_watching.append(streamers_index[:2]) - elif prior == Priority.STREAK and len(streamers_watching) <= 2: + streamers_watching += streamers_index[: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. @@ -375,7 +389,7 @@ class Twitch(object): if len(streamers_watching) == 2: break - elif prior == Priority.DROPS and len(streamers_watching) <= 2: + elif prior == Priority.DROPS and len(streamers_watching) < 2: for index in streamers_index: # For the truth we don't need al of this If - condition # because the drops_campaigns can be fulled only if claim_drops is True and drops_tags is True @@ -393,7 +407,7 @@ class Twitch(object): ] ) logger.debug( - f"{streamers[index]} it's currenty stream: {streamers[index].stream}" + f"{streamers[index]} it's currently stream: {streamers[index].stream}" ) logger.debug( f"Campaign currently active here: {len(streamers[index].stream.drops_campaigns)}, drops available: {drops_available}" @@ -422,15 +436,35 @@ class Twitch(object): ) 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.drops_campaigns: + for drop in campaign.drops: + if drop.has_preconditions_met is not False: + if 1 == 1: + print( + f"{round((drop.percentage_progress / 25), 4).is_integer()} ======================================================================================================================" + ) + logger.info(streamers[index]) + logger.info(streamers[index].stream) + logger.info(campaign) + logger.info(drop) + logger.info(drop.progress_bar()) + print( + "===========================================================================================================================" + ) + except requests.exceptions.ConnectionError as e: logger.error(f"Error while trying to watch a minute: {e}") - # Create chunk of sleep of speed-up the break loop after CTRL+C - sleep_time = max(next_iteration - time.time(), 0) / chunk_size - for i in range(0, chunk_size): - time.sleep(sleep_time) - if self.running is False: - break + self.__chuncked_sleep( + next_iteration - time.time(), chunk_size=chunk_size + ) if streamers_watching == []: time.sleep(60) diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index e79a4b8..ce64b42 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -1,6 +1,7 @@ from datetime import datetime from TwitchChannelPointsMiner.classes.entities.Drop import Drop +from TwitchChannelPointsMiner.classes.Settings import Settings class Campaign(object): @@ -20,6 +21,13 @@ class Campaign(object): 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 progress: {self.in_inventory}" + if Settings.logger.less + else self.__repr__() + ) + def clear_drops(self): self.drops = [ drop diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py index a1d6cee..52c7253 100644 --- a/TwitchChannelPointsMiner/classes/entities/Drop.py +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -1,5 +1,7 @@ from datetime import datetime +from TwitchChannelPointsMiner.classes.Settings import Settings + class Drop(object): def __init__(self, dict): @@ -31,16 +33,31 @@ class Drop(object): self.percentage_progress = ( 0 if self.current_minutes_watched == 0 - else int( - (self.current_minutes_watched / self.current_minutes_watched) * 100 - ) + else int((self.current_minutes_watched / self.minutes_required) * 100) ) self.is_claimable = ( self.is_claimed is False and self.drop_instance_id is not None ) 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}, drop_instance_id={self.drop_instance_id}, is_claimed={self.is_claimed})" + 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 ( + ("|" + ("█" * progress) + (" " * remaining) + "|") + + f"\t{self.percentage_progress}% [{self.current_minutes_watched}/{self.minutes_required}]" + ) def __eq__(self, other): if isinstance(other, self.__class__): From 0e483a0783804537fc01b1dace50dbfebfd1e0d7 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 20:55:14 +0100 Subject: [PATCH 072/124] Cast to str for Windows user and emoji disabled https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/68\#issuecomment-778334582 --- TwitchChannelPointsMiner/classes/Twitch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c9f5a5f..f6778c8 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -450,11 +450,11 @@ class Twitch(object): print( f"{round((drop.percentage_progress / 25), 4).is_integer()} ======================================================================================================================" ) - logger.info(streamers[index]) - logger.info(streamers[index].stream) - logger.info(campaign) - logger.info(drop) - logger.info(drop.progress_bar()) + logger.info(f"{streamers[index]}") + logger.info(f"{streamers[index].stream}") + logger.info(f"{campaign}") + logger.info(f"{drop}") + logger.info(f"{drop.progress_bar()}") print( "===========================================================================================================================" ) From 9a36dbd862b455fcecb49b4399410359f6401923 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 22:12:56 +0100 Subject: [PATCH 073/124] Use reconnect flag for manage ws reconnection and main threads --- .../classes/WebSocketsPool.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 7d1193d..f1f6a8f 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -81,19 +81,22 @@ class WebSocketsPool: 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: - ws.ping() - time.sleep(random.uniform(25, 30)) + # 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_reconneting is False: + ws.ping() # We need ping for keep the connection alive + time.sleep(random.uniform(25, 30)) - if ws.elapsed_last_pong() > 5 and ws.is_reconneting is False: - logger.info( - f"#{ws.index} - The last PONG was received more than 5 minutes ago" - ) - ws.is_reconneting = True - WebSocketsPool.handle_reconnection(ws) + 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 = threading.Thread(target=run) thread_ws.daemon = True @@ -117,6 +120,11 @@ class WebSocketsPool: 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 exsternal ping check will be locked + ws.is_reconneting = True + if ws.forced_close is False: logger.info( f"#{ws.index} - Reconnecting to Twitch PubSub server in ~60 seconds" @@ -365,7 +373,6 @@ class WebSocketsPool: elif response["type"] == "RECONNECT": logger.info(f"#{ws.index} - Reconnection required") - ws.is_reconneting = True WebSocketsPool.handle_reconnection(ws) elif response["type"] == "PONG": From 69f09444c73ed827d034cc0777dcf78b7c2add57 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 12 Feb 2021 22:13:54 +0100 Subject: [PATCH 074/124] Raise exception on wrong streamer username (special chars and space) --- TwitchChannelPointsMiner/classes/Twitch.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 5f065d6..cc667eb 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -319,11 +319,14 @@ class Twitch(object): def get_channel_id(self, streamer_username): json_response = self.__do_helix_request(f"/users?login={streamer_username}") - data = json_response["data"] - if len(data) >= 1: - return data[0]["id"] - else: + if "data" not in json_response: raise StreamerDoesNotExistException + else: + data = json_response["data"] + if len(data) >= 1: + return data[0]["id"] + else: + raise StreamerDoesNotExistException def get_followers(self, first=100): followers = [] From a8baad782c6b66a0eb45e8a6a6111a61eb7002e4 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 11:04:57 +0100 Subject: [PATCH 075/124] use to_login instead of to_name, this will fix: https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/68\#issuecomment-778518970 --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 13cbad4..9723d33 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -374,7 +374,7 @@ class Twitch(object): json_response = self.__do_helix_request(query) pagination = json_response["pagination"] - followers += [fw["to_name"].lower() for fw in json_response["data"]] + followers += [fw["to_login"].lower() for fw in json_response["data"]] time.sleep(random.uniform(0.3, 0.7)) if pagination == {}: From 386cbd5785934af183859143cec00e7c90c3c1d4 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 11:04:57 +0100 Subject: [PATCH 076/124] use to_login instead of to_name. Ref for: #68 and #issuecomment-778518970 --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 13cbad4..9723d33 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -374,7 +374,7 @@ class Twitch(object): json_response = self.__do_helix_request(query) pagination = json_response["pagination"] - followers += [fw["to_name"].lower() for fw in json_response["data"]] + followers += [fw["to_login"].lower() for fw in json_response["data"]] time.sleep(random.uniform(0.3, 0.7)) if pagination == {}: From 6e0ba67f80dbdc203acde24135fbd5c50f384223 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 11:48:22 +0100 Subject: [PATCH 077/124] The use of __slots__ can save more RAM. There are multiple articles online, I've read this: https://tech.oyster.com/save-ram-with-python-slots/ --- .../TwitchChannelPointsMiner.py | 15 +++++++++++ TwitchChannelPointsMiner/classes/Settings.py | 2 +- TwitchChannelPointsMiner/classes/Twitch.py | 2 ++ .../classes/TwitchLogin.py | 12 +++++++++ .../classes/WebSocketsPool.py | 2 ++ .../classes/entities/Bet.py | 17 ++++++++++++ .../classes/entities/EventPrediction.py | 14 ++++++++++ .../classes/entities/Message.py | 11 ++++++++ .../classes/entities/PubsubTopic.py | 2 ++ .../classes/entities/Raid.py | 2 ++ .../classes/entities/Stream.py | 15 +++++++++++ .../classes/entities/Streamer.py | 26 +++++++++++++++++++ 12 files changed, 119 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index e6c8560..8f436f3 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -38,6 +38,21 @@ logger = logging.getLogger(__name__) class TwitchChannelPointsMiner: + __slots__ = [ + "username", + "twitch", + "claim_drops_startup", + "streamers", + "events_predictions", + "minute_watcher_thread", + "ws_pool", + "session_id", + "running", + "start_datetime", + "original_streamers", + "logs_file", + ] + def __init__( self, username: str, diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index 8794e78..7ffbcfe 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -1,3 +1,3 @@ # Empty object shared between class class Settings(object): - pass + __slots__ = ["logger", "streamer_settings"] diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 9723d33..d5b1cf7 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -29,6 +29,8 @@ logger = logging.getLogger(__name__) class Twitch(object): + __slots__ = ["cookies_file", "user_agent", "twitch_login", "running"] + def __init__(self, username, user_agent): cookies_path = os.path.join(Path().absolute(), "cookies") Path(cookies_path).mkdir(parents=True, exist_ok=True) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 37929d1..2299efe 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -16,6 +16,18 @@ logger = logging.getLogger(__name__) class TwitchLogin(object): + __self__ = [ + "client_id", + "token", + "login_check_result", + "session", + "session", + "username", + "user_id", + "email", + "cookies", + ] + def __init__(self, client_id, username, user_agent): self.client_id = client_id self.token = None diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index f1f6a8f..67349b0 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -22,6 +22,8 @@ logger = logging.getLogger(__name__) class WebSocketsPool: + __slots__ = ["ws", "twitch", "streamers", "events_predictions"] + def __init__(self, twitch, streamers, events_predictions): self.ws = [] self.twitch = twitch diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index eaab53c..46a336f 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -42,6 +42,12 @@ class OutcomeKeys(object): class FilterCondition(object): + __slots__ = [ + "by", + "where", + "value", + ] + def __init__(self, by=None, where=None, value=None, decision=None): self.by = by self.where = where @@ -52,6 +58,15 @@ class FilterCondition(object): class BetSettings(object): + __slots__ = [ + "strategy", + "percentage", + "percentage_gap", + "max_points", + "stealth_mode", + "filter_condition", + ] + def __init__( self, strategy: Strategy = None, @@ -80,6 +95,8 @@ class BetSettings(object): class Bet(object): + __slots__ = ["outcomes", "decision", "total_users", "total_points", "settings"] + def __init__(self, outcomes: list, settings: BetSettings): self.outcomes = outcomes self.__clear_outcomes() diff --git a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py index a55f520..57ea6bc 100644 --- a/TwitchChannelPointsMiner/classes/entities/EventPrediction.py +++ b/TwitchChannelPointsMiner/classes/entities/EventPrediction.py @@ -5,6 +5,20 @@ from TwitchChannelPointsMiner.utils import float_round class EventPrediction(object): + __slots__ = [ + "streamer", + "event_id", + "title", + "created_at", + "prediction_window_seconds", + "status", + "final_result", + "box_fillable", + "bet_confirmed", + "bet_placed", + "bet", + ] + def __init__( self, streamer: Streamer, diff --git a/TwitchChannelPointsMiner/classes/entities/Message.py b/TwitchChannelPointsMiner/classes/entities/Message.py index 2e0888c..450764c 100644 --- a/TwitchChannelPointsMiner/classes/entities/Message.py +++ b/TwitchChannelPointsMiner/classes/entities/Message.py @@ -4,6 +4,17 @@ 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(".") diff --git a/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py index b7ccbb1..8972325 100644 --- a/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py +++ b/TwitchChannelPointsMiner/classes/entities/PubsubTopic.py @@ -1,4 +1,6 @@ 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 diff --git a/TwitchChannelPointsMiner/classes/entities/Raid.py b/TwitchChannelPointsMiner/classes/entities/Raid.py index df8d680..cd3a525 100644 --- a/TwitchChannelPointsMiner/classes/entities/Raid.py +++ b/TwitchChannelPointsMiner/classes/entities/Raid.py @@ -1,4 +1,6 @@ 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 diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index 59ddb1f..92b9ff8 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -10,6 +10,21 @@ logger = logging.getLogger(__name__) class Stream(object): + __slots__ = [ + "broadcast_id", + "title", + "game", + "tags", + "drops_enabled", + "viewers_count", + "__last_update", + "spade_url", + "payload", + "watch_streak_missing", + "minute_watched", + "__minute_watched_timestamp", + ] + def __init__(self): self.broadcast_id = None diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 3eee2b6..fdf22bb 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -11,6 +11,14 @@ logger = logging.getLogger(__name__) class StreamerSettings(object): + __slots__ = [ + "make_predictions", + "follow_raid", + "claim_drops", + "watch_streak", + "bet", + ] + def __init__( self, make_predictions: bool = None, @@ -37,6 +45,24 @@ class StreamerSettings(object): class Streamer(object): + __slots__ = [ + "username", + "channel_id", + "settings", + "is_online", + "stream_up", + "online_at", + "offline_at", + "channel_points", + "minute_watched_requests", + "viewer_is_mod", + "stream", + "raid", + "history", + "streamer_url", + "chat_url", + ] + def __init__(self, username, settings=None): self.username = username.lower().strip() self.channel_id = 0 From f773a92bd1ee376572882049ce73e7edd30f27c7 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 12:48:27 +0100 Subject: [PATCH 078/124] __slots__, filter and map --- .../TwitchChannelPointsMiner.py | 30 +++++++++---------- TwitchChannelPointsMiner/classes/Twitch.py | 17 ++++++----- .../classes/entities/Campaign.py | 22 ++++++++++---- .../classes/entities/Drop.py | 28 ++++++++++++----- .../classes/entities/Stream.py | 3 +- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index f267e84..f220ac9 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -42,9 +42,11 @@ class TwitchChannelPointsMiner: "username", "twitch", "claim_drops_startup", + "priority", "streamers", "events_predictions", "minute_watcher_thread", + "sync_drops_inventory_thread", "ws_pool", "session_id", "running", @@ -82,6 +84,7 @@ class TwitchChannelPointsMiner: self.streamers = [] self.events_predictions = {} self.minute_watcher_thread = None + self.sync_drops_inventory_thread = None self.ws_pool = None self.session_id = str(uuid.uuid4()) @@ -298,37 +301,34 @@ class TwitchChannelPointsMiner: if self.events_predictions != {}: print("") for event_id in self.events_predictions: + event = self.events_predictions[event_id] if ( - self.events_predictions[event_id].bet_confirmed is True - and self.events_predictions[ - event_id - ].streamer.settings.make_predictions - is True + event.bet_confirmed is True + and event.streamer.settings.make_predictions is True ): logger.info( - f"{self.events_predictions[event_id].streamer.settings.bet}", + f"{event.streamer.settings.bet}", extra={"emoji": ":wrench:"}, ) - if ( - self.events_predictions[ - event_id - ].streamer.settings.bet.filter_condition - is not None - ): + if event.streamer.settings.bet.filter_condition is not None: logger.info( - f"{self.events_predictions[event_id].streamer.settings.bet.filter_condition}", + f"{event.streamer.settings.bet.filter_condition}", extra={"emoji": ":pushpin:"}, ) logger.info( - f"{self.events_predictions[event_id].print_recap()}", + 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].channel_points + ) logger.info( - f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(self.streamers[streamer_index].channel_points - self.original_streamers[streamer_index].channel_points)}", + f"{repr(self.streamers[streamer_index])}, Total Points Gained (after farming - before farming): {_millify(gained)}", extra={"emoji": ":robot:"}, ) if self.streamers[streamer_index].history != {}: diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 9167511..2f81ce4 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -197,7 +197,7 @@ class Twitch(object): response = self.post_gql_request(GQLOperations.ViewerDropsDashboard) campaigns = response["data"]["currentUser"]["dropCampaigns"] if status is not None: - campaigns = [camp for camp in campaigns if camp["status"] == status.upper()] + campaigns = list(filter(lambda x: x["status"] == status.upper(), campaigns)) return campaigns def __get_campaigns_details(self, campaigns): @@ -210,7 +210,7 @@ class Twitch(object): } response = self.post_gql_request(json_data) - return [res["data"]["user"]["dropCampaign"] for res in response] + return list(map(lambda x: x["data"]["user"]["dropCampaign"], response)) def sync_drops_inventory(self, streamers, chunk_size=3): campaigns_update = 0 @@ -285,12 +285,13 @@ class Twitch(object): and streamers[index].stream.drops_tags is True ): # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - streamers[index].stream.drops_campaigns = [ - campaign - for campaign in campaigns - if campaign.drops != [] - and campaign.game == streamers[index].stream.game - ] + streamers[index].stream.drops_campaigns = list( + filter( + lambda x: x.drops != [] + and x.game == streamers[index].stream.game, + campaigns, + ) + ) self.__chuncked_sleep(60, chunk_size=chunk_size) diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index ce64b42..72b08eb 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -5,6 +5,18 @@ from TwitchChannelPointsMiner.classes.Settings import Settings class Campaign(object): + __slots__ = [ + "id", + "game", + "name", + "status", + "in_inventory", + "end_at", + "start_at", + "dt_match", + "drops", + ] + def __init__(self, dict): self.id = dict["id"] self.game = dict["game"] @@ -16,7 +28,7 @@ class Campaign(object): 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 = [Drop(drop) for drop in dict["timeBasedDrops"]] + 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})" @@ -29,11 +41,9 @@ class Campaign(object): ) def clear_drops(self): - self.drops = [ - drop - for drop in self.drops - if drop.dt_match is True and drop.is_claimed is False - ] + 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__): diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py index 52c7253..91e28e8 100644 --- a/TwitchChannelPointsMiner/classes/entities/Drop.py +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -4,6 +4,22 @@ from TwitchChannelPointsMiner.classes.Settings import Settings 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", + ] + def __init__(self, dict): self.id = dict["id"] self.name = dict["name"] @@ -17,6 +33,7 @@ class Drop(object): self.drop_instance_id = None self.is_claimed = False self.is_claimable = 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") @@ -30,14 +47,14 @@ class Drop(object): 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 = ( 0 if self.current_minutes_watched == 0 else int((self.current_minutes_watched / self.minutes_required) * 100) ) - self.is_claimable = ( - self.is_claimed is False and self.drop_instance_id is not None - ) 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})" @@ -54,10 +71,7 @@ class Drop(object): remaining = (100 - self.percentage_progress) // 2 if remaining + progress < 50: remaining += 50 - (remaining + progress) - return ( - ("|" + ("█" * progress) + (" " * remaining) + "|") - + f"\t{self.percentage_progress}% [{self.current_minutes_watched}/{self.minutes_required}]" - ) + return f"|{('█' * progress)}{(' ' * remaining)}|\t{self.percentage_progress}% [{self.current_minutes_watched}/{self.minutes_required}]" def __eq__(self, other): if isinstance(other, self.__class__): diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index 9be51ee..477f0cd 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -15,7 +15,8 @@ class Stream(object): "title", "game", "tags", - "drops_enabled", + "drops_tags", + "drops_campaigns", "viewers_count", "__last_update", "spade_url", From 4dbd85018415960d05dba9e53185959d8b17eda3 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 13:18:27 +0100 Subject: [PATCH 079/124] Close #70 --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index d5b1cf7..bb09117 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -234,7 +234,7 @@ class Twitch(object): extra={"emoji": ":pushpin:"}, ) else: - if decision["amount"] > 0: + if decision["amount"] >= 10: logger.info( f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", extra={"emoji": ":four_leaf_clover:"}, From 40b1b43100318394e73dbcf4c0dad0f0a45a695d Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 16:19:33 +0100 Subject: [PATCH 080/124] Create is_printable boolean value. This will be true only If the drop item have quarter percentage or move from 0 to 1. Print also only if we have a updated values. Other fix and improvements --- TwitchChannelPointsMiner/classes/Twitch.py | 42 ++++++++----------- .../classes/entities/Drop.py | 33 ++++++++++++--- TwitchChannelPointsMiner/utils.py | 4 ++ 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2f81ce4..f84e70d 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -171,7 +171,11 @@ class Twitch(object): json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} - self.post_gql_request(json_data) + response = self.post_gql_request(json_data) + try: + return response["data"]["claimDropRewards"]["status"] == "ELIGIBLE_FOR_ALL" + except (ValueError, KeyError): + return False def search_drop_in_inventory(self, streamer, drop_id): inventory = self.__get_inventory() @@ -217,6 +221,7 @@ class Twitch(object): while self.running: # Get update from dashboard each 60minutes if campaigns_update == 0 or ((time.time() - campaigns_update) / 60) > 60: + campaigns_update = time.time() # Get full details from current ACTIVE campaigns campaigns_details = self.__get_campaigns_details( self.__get_drops_dashboard(status="ACTIVE") @@ -241,7 +246,6 @@ class Twitch(object): for in_progress in inventory["dropCampaignsInProgress"]: if in_progress["id"] == campaigns[i].id: campaigns[i].in_inventory = True - # logger.info(campaigns[i]) # Iterate all the drops from inventory for drop in in_progress["timeBasedDrops"]: # Iterate all the drops from out campaigns array @@ -256,27 +260,15 @@ class Twitch(object): campaigns[i].drops[j].update(drop["self"]) # If after update we all conditions are meet we can claim the drop if campaigns[i].drops[j].is_claimable is True: - self.claim_drop(campaigns[i].drops[j]) - # logger.info(campaigns[i].drops[j]) - # logger.info(campaigns[i].drops[j].progress_bar()) + campaigns[i].drops[ + j + ].is_claimed = self.claim_drop( + campaigns[i].drops[j] + ) break # Found it! - # print("\n") - # Remove all the claime drops - campaigns[i].clear_drops() + campaigns[i].clear_drops() # Remove all the claime drops break # Found it! - """ - campaign_not_started = [ - campaign for campaign in campaigns if campaign.in_inventory is False - ] - logger.info( - f"We could start all of this campaigns: {len(campaign_not_started)}" - ) - logger.info( - f"Campaign active in dashboard: {len(campaigns)}, in progress on our inventory: {len(inventory['dropCampaignsInProgress'])}" - ) - """ - # Check if user It's currently streaming the same game present in campaigns_details for index in range(0, len(streamers)): if ( @@ -388,6 +380,7 @@ class Twitch(object): 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 == Priority.STREAK and len(streamers_watching) < 2: """ Check if we need need to change priority based on watch streak @@ -471,17 +464,16 @@ class Twitch(object): for drop in campaign.drops: if drop.has_preconditions_met is not False: if 1 == 1: - print( - f"{round((drop.percentage_progress / 25), 4).is_integer()} ======================================================================================================================" + print("=" * 125) + logger.info( + f"Drops should be printed: {drop.is_printable}" ) logger.info(f"{streamers[index]}") logger.info(f"{streamers[index].stream}") logger.info(f"{campaign}") logger.info(f"{drop}") logger.info(f"{drop.progress_bar()}") - print( - "===========================================================================================================================" - ) + print("=" * 125) except requests.exceptions.ConnectionError as e: logger.error(f"Error while trying to send minute watched: {e}") diff --git a/TwitchChannelPointsMiner/classes/entities/Drop.py b/TwitchChannelPointsMiner/classes/entities/Drop.py index 91e28e8..d4d8b52 100644 --- a/TwitchChannelPointsMiner/classes/entities/Drop.py +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -1,6 +1,7 @@ from datetime import datetime from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import percentage class Drop(object): @@ -18,6 +19,7 @@ class Drop(object): "end_at", "start_at", "dt_match", + "is_printable", ] def __init__(self, dict): @@ -33,6 +35,7 @@ class Drop(object): 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") @@ -44,17 +47,37 @@ class Drop(object): 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 = ( - 0 - if self.current_minutes_watched == 0 - else int((self.current_minutes_watched / self.minutes_required) * 100) - ) + 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})" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index da8de53..dd1b491 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -140,3 +140,7 @@ def internet_connection_available(host="8.8.8.8", port=53, timeout=3): return True except socket.error: return False + + +def percentage(a, b): + return 0 if a == 0 else int((a / b) * 100) From 67124468e42277524efdd09dbcc862739b7389e2 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sat, 13 Feb 2021 19:10:57 +0100 Subject: [PATCH 081/124] =?UTF-8?q?Edit=20emoji=20pattern.=20Fix=20progres?= =?UTF-8?q?s=20bar=20with=20emoji=3DFalse.=20I=20need=20Unicode=20Characte?= =?UTF-8?q?r=20=E2=80=9C=E2=96=88=E2=80=9D=20(U+2588)=20https://github.com?= =?UTF-8?q?/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/68\#issuecomment-?= =?UTF-8?q?778646893?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TwitchChannelPointsMiner/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index dd1b491..1785c56 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -66,10 +66,12 @@ def remove_emoji(string: str) -> str: "\U0001F300-\U0001F5FF" # symbols & pictographs "\U0001F680-\U0001F6FF" # transport & map symbols "\U0001F1E0-\U0001F1FF" # flags (iOS) - "\U00002500-\U00002BEF" # chinese char + "\U00002500-\U00002587" # chinese char + "\U00002589-\U00002BEF" # I need Unicode Character “█” (U+2588) "\U00002702-\U000027B0" "\U00002702-\U000027B0" - "\U000024C2-\U0001F251" + "\U000024C2-\U00002587" + "\U00002589-\U0001F251" "\U0001f926-\U0001f937" "\U00010000-\U0010ffff" "\u2640-\u2642" @@ -102,7 +104,7 @@ def remove_emoji(string: str) -> str: def at_least_one_value_in_settings_is(array, attr_name, condition=True): return [ - itme for itme in array if getattr(itme.settings, attr_name) == condition + item for item in array if getattr(item.settings, attr_name) == condition ] != [] From f0d4752209ac74209b909e99338e216c5ac6806c Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 11:24:05 +0100 Subject: [PATCH 082/124] Convert dict to object and fix claim on start-up. Ref: https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/68\#issuecomment-778717848 --- TwitchChannelPointsMiner/classes/Twitch.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index f84e70d..7f1564f 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -16,6 +16,7 @@ from secrets import token_hex import requests from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign +from TwitchChannelPointsMiner.classes.entities.Drop import Drop from TwitchChannelPointsMiner.classes.Exceptions import ( StreamerDoesNotExistException, StreamerIsOfflineException, @@ -188,9 +189,11 @@ class Twitch(object): def claim_all_drops_from_inventory(self): inventory = self.__get_inventory() for campaign in inventory["dropCampaignsInProgress"]: - for drop in campaign["timeBasedDrops"]: - if drop["self"]["dropInstanceID"] is not None: - self.claim_drop(drop["self"]["dropInstanceID"]) + 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 __get_inventory(self): @@ -243,11 +246,11 @@ class Twitch(object): # In this array we have also the campaigns never started from us (not in nventory) for i in range(0, len(campaigns)): # Iterate all campaigns currently in progress from out inventory - for in_progress in inventory["dropCampaignsInProgress"]: - if in_progress["id"] == campaigns[i].id: + for progress in inventory["dropCampaignsInProgress"]: + if progress["id"] == campaigns[i].id: campaigns[i].in_inventory = True # Iterate all the drops from inventory - for drop in in_progress["timeBasedDrops"]: + for drop in progress["timeBasedDrops"]: # Iterate all the drops from out campaigns array # After id match update with # - currentMinutesWatched @@ -260,11 +263,8 @@ class Twitch(object): campaigns[i].drops[j].update(drop["self"]) # If after update we all conditions are meet we can claim the drop if campaigns[i].drops[j].is_claimable is True: - campaigns[i].drops[ - j - ].is_claimed = self.claim_drop( - campaigns[i].drops[j] - ) + claimed = self.claim_drop(campaigns[i].drops[j]) + campaigns[i].drops[j].is_claimed = claimed break # Found it! campaigns[i].clear_drops() # Remove all the claime drops break # Found it! From 2771aa41c62b919ae18f608ad7c559f89e363295 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 11:27:05 +0100 Subject: [PATCH 083/124] Suppress requests module. Close #72 --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 8f436f3..1ef6ab1 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -32,7 +32,9 @@ from TwitchChannelPointsMiner.utils import ( # Suppress: # - chardet.charsetprober - [feed] # - chardet.charsetprober - [get_confidence] +# - requests - [Starting new HTTPS connection (1)] logging.getLogger("chardet.charsetprober").setLevel(logging.ERROR) +logging.getLogger("requests").setLevel(logging.ERROR) logger = logging.getLogger(__name__) From 5d0907457292f55d4d0a7354cba3f1583347cd3d Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 12:22:36 +0100 Subject: [PATCH 084/124] Little code refactory where It's possible :blush: --- .../TwitchChannelPointsMiner.py | 28 ++++------------ .../classes/Exceptions.py | 4 --- TwitchChannelPointsMiner/classes/Twitch.py | 30 +++++------------ .../classes/TwitchLogin.py | 11 ++++--- .../classes/WebSocketsPool.py | 31 ----------------- .../classes/entities/Bet.py | 6 ++-- .../classes/entities/Campaign.py | 2 +- .../classes/entities/Streamer.py | 4 +-- TwitchChannelPointsMiner/utils.py | 33 ++++++++++--------- 9 files changed, 44 insertions(+), 105 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index f220ac9..6a2811e 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -150,12 +150,11 @@ class TwitchChannelPointsMiner: for username in streamers_name: time.sleep(random.uniform(0.3, 0.7)) try: - - if isinstance(streamers_dict[username], Streamer) is True: - streamer = streamers_dict[username] - else: - streamer = Streamer(username) - + 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 @@ -163,7 +162,6 @@ class TwitchChannelPointsMiner: streamer.settings.bet = set_default_settings( streamer.settings.bet, Settings.streamer_settings.bet ) - self.streamers.append(streamer) except StreamerDoesNotExistException: logger.info( @@ -194,6 +192,7 @@ class TwitchChannelPointsMiner: target=self.twitch.sync_drops_inventory, args=(self.streamers,), ) + self.sync_drops_inventory_thread.name = "Sync drops inventory" self.sync_drops_inventory_thread.start() time.sleep(30) @@ -218,21 +217,6 @@ class TwitchChannelPointsMiner: ) ) - """ - # If we have at least one streamer with settings = claim_drops True - # Going to subscribe to user-drop-events. Get update for drop-progress - claim_drops = at_least_one_value_in_settings_is( - self.streamers, "claim_drops", True - ) - if claim_drops is True: - self.ws_pool.submit( - PubsubTopic( - "user-drop-events", - user_id=self.twitch.twitch_login.get_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( diff --git a/TwitchChannelPointsMiner/classes/Exceptions.py b/TwitchChannelPointsMiner/classes/Exceptions.py index 0d0acd0..50d6abb 100644 --- a/TwitchChannelPointsMiner/classes/Exceptions.py +++ b/TwitchChannelPointsMiner/classes/Exceptions.py @@ -8,7 +8,3 @@ class StreamerIsOfflineException(Exception): class WrongCookiesException(Exception): pass - - -class TimeBasedDropNotFound(Exception): - pass diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 7f1564f..e396be5 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -20,7 +20,6 @@ from TwitchChannelPointsMiner.classes.entities.Drop import Drop from TwitchChannelPointsMiner.classes.Exceptions import ( StreamerDoesNotExistException, StreamerIsOfflineException, - TimeBasedDropNotFound, ) from TwitchChannelPointsMiner.classes.Settings import Priority, Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin @@ -83,15 +82,13 @@ class Twitch(object): headers = {"User-Agent": self.user_agent} main_page_request = requests.get(streamer.streamer_url, headers=headers) response = main_page_request.text - settings_url = re.search( - "(https://static.twitchcdn.net/config/settings.*?js)", response - ).group(1) + 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 - streamer.stream.spade_url = re.search( - '"spade_url":"(.*?)"', response - ).group(1) + 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}") @@ -178,14 +175,6 @@ class Twitch(object): except (ValueError, KeyError): return False - def search_drop_in_inventory(self, streamer, drop_id): - inventory = self.__get_inventory() - for campaign in inventory["dropCampaignsInProgress"]: - for drop in campaign["timeBasedDrops"]: - if drop["id"] == drop_id: - return drop["self"] - raise TimeBasedDropNotFound - def claim_all_drops_from_inventory(self): inventory = self.__get_inventory() for campaign in inventory["dropCampaignsInProgress"]: @@ -415,19 +404,18 @@ class Twitch(object): and streamers[index].stream.drops_tags is True and streamers[index].stream.drops_campaigns != [] ): + stream = streamers[index].stream drops_available = sum( [ len(campaign.drops) - for campaign in streamers[ - index - ].stream.drops_campaigns + for campaign in stream.drops_campaigns ] ) logger.debug( - f"{streamers[index]} it's currently stream: {streamers[index].stream}" + f"{streamers[index]} it's currently stream: {stream}" ) logger.debug( - f"Campaign currently active here: {len(streamers[index].stream.drops_campaigns)}, drops available: {drops_available}" + f"Campaign currently active here: {len(stream.drops_campaigns)}, drops available: {drops_available}" ) streamers_watching.append(index) if len(streamers_watching) == 2: @@ -485,7 +473,7 @@ class Twitch(object): logger.warning( f"No internet connection available! Retry after {random_sleep}m" ) - time.sleep(random_sleep * 60) + self.__chuncked_sleep(random_sleep * 60, chunk_size=chunk_size) self.__chuncked_sleep( next_iteration - time.time(), chunk_size=chunk_size diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 2299efe..7c15850 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -129,9 +129,8 @@ class TwitchLogin(object): self.session.headers.update({"Authorization": f"Bearer {self.token}"}) def send_login_request(self, json_data): - r = self.session.post("https://passport.twitch.tv/login", json=json_data) - j = r.json() - return j + response = self.session.post("https://passport.twitch.tv/login", json=json_data) + return response.json() def login_flow_backup(self): """Backup OAuth login flow in case manual captcha solving is required""" @@ -161,8 +160,10 @@ class TwitchLogin(object): if self.token is None: return False - r = self.session.get(f"https://api.twitch.tv/helix/users?login={self.username}") - response = r.json() + response = self.session.get( + f"https://api.twitch.tv/helix/users?login={self.username}" + ) + response = response.json() if "data" in response: self.login_check_result = True self.user_id = response["data"][0]["id"] diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 6bfc5ac..cc6244c 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -336,37 +336,6 @@ class WebSocketsPool: ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True - - """ - elif message.topic == "user-drop-events": - if message.type == "drop-progress": - current = message.data["current_progress_min"] - required = message.data["required_progress_min"] - if current >= required: - try: - drop = ws.twitch.search_drop_in_inventory( - ws.streamers[streamer_index], - message.data["drop_id"], - ) - if drop["dropInstanceID"] is not None: - ws.twitch.claim_drop( - drop["dropInstanceID"], - ws.streamers[streamer_index], - ) - except TimeBasedDropNotFound: - logger.error( - f"Unable to find {message.data['drop_id']} in your inventory" - ) - else: - # Skip 0% and 100% ... - percentage_state = int((current / required) * 100) - if percentage_state != 0 and percentage_state % 25 == 0: - logger.info( - f"Drop event {percentage_state}% for {ws.streamers[streamer_index]}!", - extra={"emoji": ":package:"}, - ) - """ - except Exception: logger.error( f"Exception raised for topic: {message.topic} and message: {message}", diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 46a336f..033227a 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -54,7 +54,7 @@ class FilterCondition(object): self.value = value def __repr__(self): - return f"FilterCondition(By={self.by.upper()}, Where={self.where}, Value={self.value})" + return f"FilterCondition(by={self.by.upper()}, where={self.where}, value={self.value})" class BetSettings(object): @@ -91,7 +91,7 @@ class BetSettings(object): self.stealth_mode = self.stealth_mode if not None else False def __repr__(self): - return f"BetSettings(Strategy={self.strategy}, Percentage={self.percentage}, PercentageGap={self.percentage_gap}, MaxPoints={self.max_points}, StealthMode={self.stealth_mode})" + return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, stealth_mode={self.stealth_mode})" class Bet(object): @@ -153,7 +153,7 @@ class Bet(object): self.__clear_outcomes() def __repr__(self): - return f"Bet(TotalUsers={millify(self.total_users)}, TotalPoints={millify(self.total_points)}), Decision={self.decision})\n\t\tOutcome0({self.get_outcome(0)})\n\t\tOutcome1({self.get_outcome(1)})" + return f"Bet(total_users={millify(self.total_users)}, total_points={millify(self.total_points)}), decision={self.decision})\n\t\tOutcome0({self.get_outcome(0)})\n\t\tOutcome1({self.get_outcome(1)})" def get_outcome(self, index): outcome = self.outcomes[index] diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index 72b08eb..5021572 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -35,7 +35,7 @@ class Campaign(object): def __str__(self): return ( - f"{self.name}, Game: {self.game['displayName']} - Drops: {len(self.drops)} pcs. - In progress: {self.in_inventory}" + f"{self.name}, Game: {self.game['displayName']} - Drops: {len(self.drops)} pcs. - In inventory: {self.in_inventory}" if Settings.logger.less else self.__repr__() ) diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index fdf22bb..103b007 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -41,7 +41,7 @@ class StreamerSettings(object): self.bet = BetSettings() def __repr__(self): - return f"BetSettings(MakePredictions={self.make_predictions}, FollowRaid={self.follow_raid}, ClaimDrops={self.claim_drops}, WatchStreak={self.watch_streak}, Bet={self.bet})" + return f"BetSettings(make_predictions={self.make_predictions}, follow_raid={self.follow_raid}, claim_drops={self.claim_drops}, watch_streak={self.watch_streak}, bet={self.bet})" class Streamer(object): @@ -60,7 +60,6 @@ class Streamer(object): "raid", "history", "streamer_url", - "chat_url", ] def __init__(self, username, settings=None): @@ -81,7 +80,6 @@ class Streamer(object): self.history = {} self.streamer_url = f"{URL}/{self.username}" - self.chat_url = f"{URL}/popout/{self.username}/chat?popout=" def __repr__(self): return f"Streamer(username={self.username}, channel_id={self.channel_id}, channel_points={_millify(self.channel_points)})" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 1785c56..9645bb3 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -103,17 +103,20 @@ def remove_emoji(string: str) -> str: def at_least_one_value_in_settings_is(array, attr_name, condition=True): - return [ - item for item in array if getattr(item.settings, attr_name) == condition - ] != [] + return ( + list(filter(lambda x: getattr(x.settings, attr_name) == condition, array)) != [] + ) def copy_values_if_none(settings, defaults): - values = [ - name - for name in dir(settings) - if name.startswith("__") is False and callable(getattr(settings, name)) is False - ] + 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)) @@ -122,13 +125,13 @@ def copy_values_if_none(settings, defaults): def set_default_settings(settings, defaults): # If no settings was provided use the default settings ... - if settings is None: - settings = deepcopy(defaults) - else: - # If settings was provided but maybe are only partial set - # Get the default values from Settings.streamer_settings - settings = copy_values_if_none(settings, defaults) - return 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): From ebca6e2e680faec4b7c379ccf347978fe9801e87 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 18:09:18 +0100 Subject: [PATCH 085/124] Print only 'if drop.has_preconditions_met is not False and drop.is_printable is True' - Remove verbose prints for debug --- TwitchChannelPointsMiner/classes/Twitch.py | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index f84e70d..21b748e 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -462,18 +462,19 @@ class Twitch(object): for campaign in streamers[index].stream.drops_campaigns: for drop in campaign.drops: - if drop.has_preconditions_met is not False: - if 1 == 1: - print("=" * 125) - logger.info( - f"Drops should be printed: {drop.is_printable}" - ) - logger.info(f"{streamers[index]}") - logger.info(f"{streamers[index].stream}") - logger.info(f"{campaign}") - logger.info(f"{drop}") - logger.info(f"{drop.progress_bar()}") - print("=" * 125) + # We could add .has_preconditions_met condition inside is_printable + if ( + drop.has_preconditions_met is not False + and drop.is_printable is True + ): + # print("=" * 125) + logger.info( + f"{streamers[index]} is streaming {streamers[index].stream}" + ) + logger.info(f"Campaign: {campaign}") + logger.info(f"Drop: {drop}") + logger.info(f"{drop.progress_bar()}") + # print("=" * 125) except requests.exceptions.ConnectionError as e: logger.error(f"Error while trying to send minute watched: {e}") From c55f6a05340cd37a04abea791b1a15bea790e773 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 22:21:39 +0100 Subject: [PATCH 086/124] Spawn thread only if at_least_one_value_in_settings_is True --- .../TwitchChannelPointsMiner.py | 29 ++++++++++++------- TwitchChannelPointsMiner/utils.py | 9 +++--- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 6a2811e..9247731 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -188,13 +188,19 @@ class TwitchChannelPointsMiner: self.streamers, "make_predictions", True ) - self.sync_drops_inventory_thread = threading.Thread( - target=self.twitch.sync_drops_inventory, - args=(self.streamers,), - ) - self.sync_drops_inventory_thread.name = "Sync drops inventory" - self.sync_drops_inventory_thread.start() - time.sleep(30) + # 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_drops_inventory_thread = threading.Thread( + target=self.twitch.sync_drops_inventory, + args=(self.streamers,), + ) + self.sync_drops_inventory_thread.name = "Sync drops inventory" + self.sync_drops_inventory_thread.start() + time.sleep(30) self.minute_watcher_thread = threading.Thread( target=self.twitch.send_minute_watched_events, @@ -260,10 +266,13 @@ class TwitchChannelPointsMiner: self.running = self.twitch.running = False self.ws_pool.end() - self.minute_watcher_thread.join() - self.sync_drops_inventory_thread.join() - time.sleep(1) + if self.minute_watcher_thread is not None: + self.minute_watcher_thread.join() + if self.sync_drops_inventory_thread is not None: + self.sync_drops_inventory_thread.join() + + time.sleep(1) self.__print_report() sys.exit(0) diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index 9645bb3..0a4ab14 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -102,10 +102,11 @@ def remove_emoji(string: str) -> str: return emoji_pattern.sub(r"", string) -def at_least_one_value_in_settings_is(array, attr_name, condition=True): - return ( - list(filter(lambda x: getattr(x.settings, attr_name) == condition, array)) != [] - ) +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): From a6c6857e2a5e5f947334a7c302c44a878875c4e3 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Sun, 14 Feb 2021 22:57:16 +0100 Subject: [PATCH 087/124] Try Exception for thread sync_drops_inventory - Handling connection error Exception --- TwitchChannelPointsMiner/classes/Twitch.py | 160 ++++++++++++--------- 1 file changed, 92 insertions(+), 68 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 70394c8..ac89de8 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -211,68 +211,90 @@ class Twitch(object): def sync_drops_inventory(self, streamers, chunk_size=3): campaigns_update = 0 while self.running: - # Get update from dashboard each 60minutes - if campaigns_update == 0 or ((time.time() - campaigns_update) / 60) > 60: - campaigns_update = time.time() - # Get full details from current ACTIVE campaigns - 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)): - 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) - - # Get data from inventory and sync current status with streamers.drops_campaigns - inventory = self.__get_inventory() - # 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(0, len(campaigns)): - # 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 - # Iterate all the drops from inventory - for drop in progress["timeBasedDrops"]: - # Iterate all the drops from out campaigns array - # After id match update with - # - currentMinutesWatched - # - hasPreconditionsMet - # - dropInstanceID - # - isClaimed - for j in range(0, len(campaigns[i].drops)): - current_id = campaigns[i].drops[j].id - if drop["id"] == current_id: - campaigns[i].drops[j].update(drop["self"]) - # If after update we all conditions are meet we can claim the drop - if campaigns[i].drops[j].is_claimable is True: - claimed = self.claim_drop(campaigns[i].drops[j]) - campaigns[i].drops[j].is_claimed = claimed - break # Found it! - campaigns[i].clear_drops() # Remove all the claime drops - break # Found it! - - # Check if user It's currently streaming the same game present in campaigns_details - for index in range(0, len(streamers)): + try: + # Get update from dashboard each 60minutes if ( - streamers[index].settings.claim_drops is True - and streamers[index].is_online is True - and streamers[index].stream.drops_tags is True + campaigns_update == 0 + or ((time.time() - campaigns_update) / 60) > 60 ): - # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - streamers[index].stream.drops_campaigns = list( - filter( - lambda x: x.drops != [] - and x.game == streamers[index].stream.game, - campaigns, - ) + campaigns_update = time.time() + # Get full details from current ACTIVE campaigns + campaigns_details = self.__get_campaigns_details( + self.__get_drops_dashboard(status="ACTIVE") ) + if campaigns_details not in [{}, None, []]: + campaigns = [] + + # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet + for index in range(0, len(campaigns_details)): + 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) + + # Get data from inventory and sync current status with streamers.drops_campaigns + inventory = self.__get_inventory() + if inventory 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(0, len(campaigns)): + # 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 + # Iterate all the drops from inventory + for drop in progress["timeBasedDrops"]: + # Iterate all the drops from out campaigns array + # After id match update with + # - currentMinutesWatched + # - hasPreconditionsMet + # - dropInstanceID + # - isClaimed + for j in range(0, len(campaigns[i].drops)): + current_id = campaigns[i].drops[j].id + if drop["id"] == current_id: + campaigns[i].drops[j].update(drop["self"]) + # If after update we all conditions are meet we can claim the drop + if ( + campaigns[i].drops[j].is_claimable + is True + ): + claimed = self.claim_drop( + campaigns[i].drops[j] + ) + campaigns[i].drops[ + j + ].is_claimed = claimed + break # Found it! + campaigns[ + i + ].clear_drops() # Remove all the claime drops + break # Found it! + + # Check if user It's currently streaming the same game present in campaigns_details + for index in range(0, len(streamers)): + if ( + streamers[index].settings.claim_drops is True + and streamers[index].is_online is True + and streamers[index].stream.drops_tags is True + ): + # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! + streamers[index].stream.drops_campaigns = list( + filter( + lambda x: x.drops != [] + and x.game == streamers[index].stream.game, + campaigns, + ) + ) + else: + logger.error("Error! Inventory is empty") + self.__check_connection_handler(chunk_size) + + except 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) @@ -466,15 +488,7 @@ class Twitch(object): except requests.exceptions.ConnectionError as e: logger.error(f"Error while trying to send minute watched: {e}") - - # 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) + self.__check_connection_handler(chunk_size) self.__chuncked_sleep( next_iteration - time.time(), chunk_size=chunk_size @@ -483,6 +497,16 @@ class Twitch(object): if streamers_watching == []: self.__chuncked_sleep(60, chunk_size=chunk_size) + 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 get_channel_id(self, streamer_username): json_response = self.__do_helix_request(f"/users?login={streamer_username}") if "data" not in json_response: From 0ad4bbbb318567e5cef6b0b43ff0402a1f1ad91b Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 00:16:53 +0100 Subject: [PATCH 088/124] ValueError, KeyError --- TwitchChannelPointsMiner/classes/Twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index ac89de8..cafa013 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -292,7 +292,7 @@ class Twitch(object): logger.error("Error! Inventory is empty") self.__check_connection_handler(chunk_size) - except requests.exceptions.ConnectionError as e: + except (ValueError, KeyError, requests.exceptions.ConnectionError) as e: logger.error(f"Error while syncing inventory: {e}") self.__check_connection_handler(chunk_size) From 16d84170c2dc49041625799fe6b86e4d829bc64d Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 00:23:36 +0100 Subject: [PATCH 089/124] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 09ddf5d..764ec0e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ If you have any type of issue, you need help, or you just want to suggest a new If you want to help on this project please leave a star 🌟 and share with your friends! 😎 +A coffee is always a sign of LOVE ❤️ + +Buy Me A Coffee + ## Main difference from the original repository: - Improve the logging From 622284e6c2e9c7509fe14f9ead2ebf3117e33b6e Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 14:39:07 +0100 Subject: [PATCH 090/124] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 764ec0e..a02955a 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,7 @@ Other usefully infos can be founded 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=hoPyNwAk97U&t=1s). It's for the first version of the miner, but the setup It's the same. +You can also follow this [video tutorial](https://www.youtube.com/watch?v=0VkM7NOZkuA). ## Issue / Debug When you open a new issue please use the correct **template**. Please provide at least the following information/files: From 80ddd28676c8bc2f28dcc56fad87cfc32010b93c Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 15:31:54 +0100 Subject: [PATCH 091/124] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md 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 From d342413a81c549fac8af3cc02398b229269e7059 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 15:39:18 +0100 Subject: [PATCH 092/124] Create pull_request_template.md --- .../pull_request_template.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..22121ab --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/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 to not 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 own code +- [ ] I have commented 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 From 877e7b1339292d5a1cd074ea5056aa6b0b8c5f77 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 19:56:55 +0100 Subject: [PATCH 093/124] PULL_REQUEST_TEMPLATE.md --- .../pull_request_template.md => PULL_REQUEST_TEMPLATE.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/pull_request_template.md => PULL_REQUEST_TEMPLATE.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE.md From 30da4632426e0640bc5ae2d57d4511013f66c22a Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 20:08:49 +0100 Subject: [PATCH 094/124] Create PULL_REQUEST_TEMPLATE.md --- PULL_REQUEST_TEMPLATE.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..22121ab --- /dev/null +++ b/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 to not 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 own code +- [ ] I have commented 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 From ab3a9d7bc5153cebb73e97c82adc741e4ccfdbf5 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 20:09:11 +0100 Subject: [PATCH 095/124] Delete PULL_REQUEST_TEMPLATE.md --- PULL_REQUEST_TEMPLATE.md | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 22121ab..0000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,26 +0,0 @@ -# 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 to not 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 own code -- [ ] I have commented 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 From 8b38e2a2f5e1f84fc12208c54ff2b38fb1f179d4 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 20:26:25 +0100 Subject: [PATCH 096/124] CONTRIBUTING.md --- CONTRIBUTING.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..53d635e --- /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 issued + +### 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, others 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 how to resolve 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 a issue please use the appropriate template and lable. + +### :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 if used when omething isn't working +- The `documentation` label if used when you suggest improvements or additions to documentation (README.md update) +- The `duplicate` label if used when this issue or pull request already exists +- The `enhancement` label if used when you ask for / or propose new feature or request +- The `help wanted` if used when label when you need help for something +- The `improvements` label if used when you would suggest improvements on already existing features +- The `invalid` label if used for non-valid issue +- The `question` label if used for further information is requested +- The `wontfix` label if used if we will not work on it + +## Opening a pull request +You can use the GitHub user interface :pencil2: for some small 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 linted 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). From 8d8ba2cd0de839df27b4137547d7c12c60aae6df Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 20:28:58 +0100 Subject: [PATCH 097/124] Update README.md --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a02955a..8bd0861 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - [Example](#example) 6. 🍪 [Migrating from old repository (the original one)](#migrating-from-old-repository-the-original-one) 7. 🪟 [Windows](#windows) -8. 🐛 [Issue / Debug](#issue--debug) +8. 🐛 [Contributing](#contributing) 9. ⚠️ [Disclaimer](#disclaimer) @@ -336,11 +336,7 @@ Other usefully infos can be founded here: You can also follow this [video tutorial](https://www.youtube.com/watch?v=0VkM7NOZkuA). ## Issue / Debug -When you open a new issue please use the correct **template**. -Please provide at least the following information/files: -- Operation System -- Python Version -- Log debug file `LoggerSettings(file_level=logging.DEBUG)` +Read [CONTRIBUTING.md](/CONTRIBUTING.md) Make sure also to have the latest commit. From 86b07d446dab892a202d3a16cefefbac32bc8593 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 15 Feb 2021 20:30:55 +0100 Subject: [PATCH 098/124] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8bd0861..f9377f9 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,7 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - [Example](#example) 6. 🍪 [Migrating from old repository (the original one)](#migrating-from-old-repository-the-original-one) 7. 🪟 [Windows](#windows) -8. 🐛 [Contributing](#contributing) -9. ⚠️ [Disclaimer](#disclaimer) +8. ⚠️ [Disclaimer](#disclaimer) ## Community @@ -49,6 +48,8 @@ A coffee is always a sign of LOVE ❤️ Buy Me A Coffee +If you have any issue, or you want to contribute you are welcome! But please before read the [CONTRIBUTING.md](/CONTRIBUTING.md) + ## Main difference from the original repository: - Improve the logging @@ -335,10 +336,6 @@ Other usefully infos can be founded here: - 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). -## Issue / Debug -Read [CONTRIBUTING.md](/CONTRIBUTING.md) - -Make sure also to have the latest commit. ## Disclaimer This project comes with no gurantee 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. From 0f0fdf2abea8d99f9337f68f6cb1bd718c2741fb Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Wed, 17 Feb 2021 23:16:11 +0100 Subject: [PATCH 099/124] Fix some typos # Description Fixed a couple of typos and changed main difference from original to main differences from original Fixes no issue ## Type of change Please delete options that are not relevant. Updated documentation # 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: - [x] My changes generate no new warnings (no code has changed) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f9377f9..ab8d4d5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p # README Contents 1. 🤝 [Community](#community) -2. 🚀 [Main difference from the original repository](#main-difference-from-the-original-repository) +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) @@ -50,7 +50,7 @@ A coffee is always a sign of LOVE ❤️ If you have any issue, or you want to contribute you are welcome! But please before read the [CONTRIBUTING.md](/CONTRIBUTING.md) -## Main difference from the original repository: +## Main differences from the original repository: - Improve the logging - Final report with all the datas @@ -288,7 +288,7 @@ 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 percentage are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percatage are highter thant `percentage_gap` select the highest percentage, else the highest odds. +- **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 are 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 | From b9d1bc654c84a626bbba1992f564ee84ad749ae2 Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:23:09 +0200 Subject: [PATCH 100/124] Update CONTRIBUTING.md --- CONTRIBUTING.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53d635e..0e808ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,12 +22,12 @@ 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). +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, others 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 how to resolve merge conflicts and other issues. +- 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: @@ -53,10 +53,10 @@ Now that you're a part of the GitHub Docs community, you can keep participating - [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: . +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 a issue please use the appropriate template and lable. +[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. @@ -68,18 +68,18 @@ 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 if used when omething isn't working +- The `bug` label is used when something isn't working - The `documentation` label if used when you suggest improvements or additions to documentation (README.md update) -- The `duplicate` label if used when this issue or pull request already exists -- The `enhancement` label if used when you ask for / or propose new feature or request -- The `help wanted` if used when label when you need help for something +- 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 labeling when you need help with something - The `improvements` label if used when you would suggest improvements on already existing features -- The `invalid` label if used for non-valid issue -- The `question` label if used for further information is requested +- The `invalid` label if used for a non-valid issue +- The `question` label, if used for further information is requested - The `wontfix` label if used if we will not work on it ## Opening a pull request -You can use the GitHub user interface :pencil2: for some small 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. +You can use the GitHub user interface :pencil2: for some small 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. @@ -97,7 +97,7 @@ For content changes, make sure that you: 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 linted with [Black](https://github.com/psf/black) using the default settings. Your code will not be accepted if it is not blackened. +All Python code is lined 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 From 32922c7a720bf8add31bd4182349a636c2ff7de2 Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:26:51 +0200 Subject: [PATCH 101/124] Update CONTRIBUTING.md awww maaaaan --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e808ae..409bbcf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,14 +69,14 @@ 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 if used when you suggest improvements or additions to documentation (README.md update) +- 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 labeling when you need help with something -- The `improvements` label if used when you would suggest improvements on already existing features -- The `invalid` label if used for a non-valid issue -- The `question` label, if used for further information is requested -- The `wontfix` label if used if we will not work on it +- 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 some small 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. From 8deaeba11bcf6dcfb6f4188861dca7018d2fd47a Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:35:51 +0200 Subject: [PATCH 102/124] Update TwitchChannelPointsMiner.py ayo --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 1ef6ab1..156bdad 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -66,7 +66,7 @@ class TwitchChannelPointsMiner: ): self.username = username - # Set as globally config + # Set as global config Settings.logger = logger_settings # Init as default all the missing values @@ -169,7 +169,7 @@ class TwitchChannelPointsMiner: # Populate the streamers with default values. # 1. Load channel points and auto-claim bonus - # 2. Check if streamers is online + # 2. Check if streamers are online # 3. Check if the user is a Streamer. In thi case you can't do prediction for streamer in self.streamers: time.sleep(random.uniform(0.3, 0.7)) @@ -263,7 +263,7 @@ class TwitchChannelPointsMiner: WebSocketsPool.handle_reconnection(self.ws_pool.ws[index]) def end(self, signum, frame): - logger.info("CTRL+C Detected! Please wait just a moments!") + logger.info("CTRL+C Detected! Please wait just a moment!") self.running = self.twitch.running = False self.ws_pool.end() From bba82af892e2472d9a94ff79d06062ef8ad3509b Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:39:24 +0200 Subject: [PATCH 103/124] Update WebSocketsPool.py lolz --- TwitchChannelPointsMiner/classes/WebSocketsPool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 67349b0..247ed6f 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -32,7 +32,7 @@ class WebSocketsPool: """ API Limits - - Clients can listen on up to 50 topics per connection. Trying to listen on more topics will result in an error message. + - 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. """ @@ -124,7 +124,7 @@ class WebSocketsPool: # Reconnect only if ws.forced_close is False (replace the keep_running) # Set the current socket as reconnecting status - # So the exsternal ping check will be locked + # So the external ping check will be locked ws.is_reconneting = True if ws.forced_close is False: From 2a232353806b238fc10b8f5cb1b994d12a065dc6 Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:45:30 +0200 Subject: [PATCH 104/124] Update PULL_REQUEST_TEMPLATE.md Biggest typo the planet earth ever had. --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 22121ab..ea12473 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,13 +14,13 @@ Please delete options that are not relevant. # 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 +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 own code -- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] 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 From 82e5c0127d3ea848ad6e7ed576411a92c4adddf5 Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 04:39:19 +0200 Subject: [PATCH 105/124] Update README.md I actually think this is the biggest typos/grammar fix yet --- README.md | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f9377f9..5c97037 100644 --- a/README.md +++ b/README.md @@ -40,25 +40,25 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p ## Community -If you have any type of issue, you need help, or you just want to suggest a new feature please open a GitHub Issue. Don't write me on [Instagram](https://www.instagram.com/tkd_alex/), [Telegram](https://t.me/TkdAlex), [Discord](https://discordapp.com/users/641397388132483121), [Twitter](https://twitter.com/TkdAxel) (but you can follow me 😆) or somewhere else. If you don't have an account on this platform you can create, It's free. I do not want to be rude, but if you have a problem, maybe another user can have also the same problem and your issue can help the community. Same for the new feature, your idea can help other users, and It's beautiful to discuss between us. +If you have any type of issue, you need help, or you just want to suggest a new feature, please open a GitHub Issue. Don't write me on [Instagram](https://www.instagram.com/tkd_alex/), [Telegram](https://t.me/TkdAlex), [Discord](https://discordapp.com/users/641397388132483121), [Twitter](https://twitter.com/TkdAxel) (but you can follow me 😆) or somewhere else. If you don't have an account on this platform, you can create it. It's free. I do not want to be rude, but if you have a problem, maybe another user can also have the same problem, and your issue can help the community. Same for the new feature, your idea can help other users, and It's beautiful to discuss between us. -If you want to help on this project please leave a star 🌟 and share with your friends! 😎 +If you want to help on this project, please leave a star 🌟 and share it with your friends! 😎 A coffee is always a sign of LOVE ❤️ Buy Me A Coffee -If you have any issue, or you want to contribute you are welcome! But please before read the [CONTRIBUTING.md](/CONTRIBUTING.md) +If you have any issues or you want to contribute, you are welcome! But please before read the [CONTRIBUTING.md](/CONTRIBUTING.md) ## Main difference from the original repository: - Improve the logging -- Final report with all the datas +- Final report with all the data - Rewrite the entire code using classe instead of module with global variables -- Automatic download the followers list and use as input +- Automatic download the follower's list and use it as input - Better 'Watch Streak' strategy in priority system [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) - Auto claim game drops from Twitch inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) Read more about game drops [here](https://help.twitch.tv/s/article/mission-based-drops) -- Place the bet / make prediction and won or lose (🍀) your channel points! +- Place the bet / make a prediction and win or lose (🍀) your channel points! No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ([@lay295](https://github.com/lay295)) ## Logs feature @@ -178,13 +178,13 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, Streame twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", - claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup + claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on the startup logger_settings=LoggerSettings( - save=True, # If you want to save logs in file (suggested) + 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) - 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 to print emoji. Set to false if you have a problem - less=False # If you think that the logs are too much verborse set this to True + 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 ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction @@ -206,12 +206,12 @@ twitch_miner = TwitchChannelPointsMiner( ) ) -# You can customize the settings for each streamer. If not settings was provided the script will use the streamer_settings from TwitchChannelPointsMiner. -# If no streamer_settings provided in TwitchChannelPointsMiner the script will use default settings. +# 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. +# 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.mine( @@ -244,7 +244,7 @@ twitch_miner.mine(["streamer1", "streamer2"], followers=True) # Mixed ### Limits > 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. -Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` Twitch return the streamers order by followed_at. So your last follow have the highest priority. +Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` Twitch return the streamers order by followed_at. So your last follow has the highest priority. ## Settings @@ -254,15 +254,15 @@ Make sure to write the streamers array in order of priority from left to right. | `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. | -| `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 to print emoji. Set to false if you have a problem | +| `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 | ### 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 values is True the script will increase the watch-time for the current game. With this you are able to claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | -| `watch_streak` | bool | True | Choose if you want to change priority for this streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you are able to claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `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 | ### BetSettings | Key | Type | Default | Description | @@ -278,8 +278,8 @@ Make sure to write the streamers array in order of priority from left to right. - **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 of select LOWEST_ODDS -- **SMART**: If the majority in percent chose an option then follow the other users, otherwise choose 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**: If the majority in percent chose an option, then follow the other users, otherwise choose the option with the highest odds ![Screenshot](./assets/prediction.png) @@ -288,7 +288,7 @@ 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 percentage are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percatage are highter thant `percentage_gap` select the highest percentage, else the highest odds. +- **SMART**: Calculate the percentage based on the users. The percentage 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 | @@ -310,15 +310,15 @@ Allowed values for `by` are: 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 are greater than 200 +- 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 1.3 +- 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 highest bet is lower than 2000 +- 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)` -## Migrating from old repository (the original one): -If you already have a `twitch-cookies.pkl` and you don't want to login again please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` +## Migrating from an old repository (the original one): +If you already have a `twitch-cookies.pkl` and you don't want to login 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 @@ -331,11 +331,11 @@ Other users have find multiple problems on Windows my suggestion are: - Stop use Windows :stuck_out_tongue_closed_eyes: - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` -Other usefully infos can be founded here: +Other useful info can be founded 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). ## Disclaimer -This project comes with no gurantee 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. +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. From 7a8146b6d007ec691c57135c1b8c15d34385fa00 Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 04:39:44 +0200 Subject: [PATCH 106/124] Update example.py I copied a part from the README.md file since I was lazy doing the same work from this original file I hope I didn't cause any problems :/ --- example.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/example.py b/example.py index be6dc22..3bea1b7 100644 --- a/example.py +++ b/example.py @@ -8,13 +8,13 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, Streame twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", - claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup + claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on the startup logger_settings=LoggerSettings( - save=True, # If you want to save logs in file (suggested) + 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) - 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 to print emoji. Set to false if you have a problem - less=False # If you think that the logs are too much verborse set this to True + 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 ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction @@ -29,19 +29,19 @@ twitch_miner = TwitchChannelPointsMiner( stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 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, # The key must be [GT, LT, GTE, LTE] than value + 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 was provided the script will use the streamer_settings from TwitchChannelPointsMiner. -# If no streamer_settings provided in TwitchChannelPointsMiner the script will use default settings. +# 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. +# 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.mine( From 92052122a1be67948c28dfee24e2a3b34c2c426f Mon Sep 17 00:00:00 2001 From: kelukelol <76735490+kelukelol@users.noreply.github.com> Date: Thu, 18 Feb 2021 16:22:29 +0200 Subject: [PATCH 107/124] Update CONTRIBUTING.md Awww maaaaaaan --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 409bbcf..19a7f0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,7 +97,7 @@ For content changes, make sure that you: 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 lined with [Black](https://github.com/psf/black) using the default settings. Your code will not be accepted if it is not blackened. +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 From 8a2f7c10174568fa323bce5403757308add8b3e6 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Mon, 15 Feb 2021 14:08:35 +0100 Subject: [PATCH 108/124] Sort history keys when printing history --- TwitchChannelPointsMiner/classes/entities/Streamer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index fdf22bb..3c2a464 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -112,7 +112,7 @@ class Streamer(object): return ", ".join( [ f"{key}({self.history[key]['counter']} times, {_millify(self.history[key]['amount'])} gained)" - for key in self.history + for key in sorted(self.history) if self.history[key]["counter"] != 0 ] ) From f0f9522c5fc7d208d08a11cac97deec87b8b182b Mon Sep 17 00:00:00 2001 From: Thomas Couchoud Date: Thu, 18 Feb 2021 18:21:27 +0100 Subject: [PATCH 109/124] Update README.md --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ef3d54f..fcd4363 100644 --- a/README.md +++ b/README.md @@ -149,18 +149,13 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner 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 - 💰 WATCH(35 times, 350 gained), CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained) -%d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username1, channel_id=0000000, channel_points=4240), Total points gained (after farming - before farming): 0 +%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 - 💰 WATCH(11 times, 132 gained), REFUND(1 times, 605 gained), CLAIM(4 times, 240 gained) -%d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username3, channel_id=0000000, channel_points=6815), Total points gained (after farming - before farming): 0 -%d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username4, channel_id=0000000, channel_points=16386), Total points gained (after farming - before farming): 0 +%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 - 💰 WATCH(53 times, 530 gained), CLAIM(17 times, 850 gained) +%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 - 💰 WATCH(42 times, 420 gained), WATCH_STREAK(1 times, 450 gained), CLAIM(14 times, 700 gained) -%d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username7, channel_id=0000000, channel_points=2380), Total points gained (after farming - before farming): 0 -%d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username8, channel_id=0000000, channel_points=10230), Total points gained (after farming - before farming): 0 +%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: From ca8958d58beb178e6dad809ffd8d0764d7efe41c Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 18 Feb 2021 21:53:31 +0100 Subject: [PATCH 110/124] Core refactory. Get the code from improvement-drops-2 - Remove all references to drops_timeout - it was a 'flop' --- .../TwitchChannelPointsMiner.py | 16 +- TwitchChannelPointsMiner/classes/Twitch.py | 153 +++++++++--------- .../classes/entities/Campaign.py | 16 ++ .../classes/entities/Drop.py | 2 +- .../classes/entities/Stream.py | 5 +- .../classes/entities/Streamer.py | 8 + TwitchChannelPointsMiner/constants.py | 9 ++ 7 files changed, 122 insertions(+), 87 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 335842e..ae24059 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -48,7 +48,7 @@ class TwitchChannelPointsMiner: "streamers", "events_predictions", "minute_watcher_thread", - "sync_drops_inventory_thread", + "sync_campaigns_thread", "ws_pool", "session_id", "running", @@ -86,7 +86,7 @@ class TwitchChannelPointsMiner: self.streamers = [] self.events_predictions = {} self.minute_watcher_thread = None - self.sync_drops_inventory_thread = None + self.sync_campaigns_thread = None self.ws_pool = None self.session_id = str(uuid.uuid4()) @@ -196,12 +196,12 @@ class TwitchChannelPointsMiner: at_least_one_value_in_settings_is(self.streamers, "claim_drops", True) is True ): - self.sync_drops_inventory_thread = threading.Thread( - target=self.twitch.sync_drops_inventory, + self.sync_campaigns_thread = threading.Thread( + target=self.twitch.sync_campaigns, args=(self.streamers,), ) - self.sync_drops_inventory_thread.name = "Sync drops inventory" - self.sync_drops_inventory_thread.start() + self.sync_campaigns_thread.name = "Sync drops inventory" + self.sync_campaigns_thread.start() time.sleep(30) self.minute_watcher_thread = threading.Thread( @@ -271,8 +271,8 @@ class TwitchChannelPointsMiner: if self.minute_watcher_thread is not None: self.minute_watcher_thread.join() - if self.sync_drops_inventory_thread is not None: - self.sync_drops_inventory_thread.join() + if self.sync_campaigns_thread is not None: + self.sync_campaigns_thread.join() time.sleep(1) self.__print_report() diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 5b64004..20d21b7 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -72,11 +72,31 @@ class Twitch(object): and streamer.settings.claim_drops is True ): event_properties["game"] = streamer.stream.game_name() + # 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_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_spade_url(self, streamer): try: headers = {"User-Agent": self.user_agent} @@ -208,7 +228,30 @@ class Twitch(object): response = self.post_gql_request(json_data) return list(map(lambda x: x["data"]["user"]["dropCampaign"], response)) - def sync_drops_inventory(self, streamers, chunk_size=3): + 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"] is not 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 + ) + campaigns[i].clear_drops() # Remove all the claimed drops + break + return campaigns + + def sync_campaigns(self, streamers, chunk_size=3): campaigns_update = 0 while self.running: try: @@ -219,78 +262,38 @@ class Twitch(object): ): campaigns_update = time.time() # 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") ) - if campaigns_details not in [{}, None, []]: - campaigns = [] + campaigns = [] - # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet - for index in range(0, len(campaigns_details)): - 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) + # Going to clear array and structure. Remove all the timeBasedDrops expired or not started yet + for index in range(0, len(campaigns_details)): + 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) - # Get data from inventory and sync current status with streamers.drops_campaigns - inventory = self.__get_inventory() - if inventory 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(0, len(campaigns)): - # 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 - # Iterate all the drops from inventory - for drop in progress["timeBasedDrops"]: - # Iterate all the drops from out campaigns array - # After id match update with - # - currentMinutesWatched - # - hasPreconditionsMet - # - dropInstanceID - # - isClaimed - for j in range(0, len(campaigns[i].drops)): - current_id = campaigns[i].drops[j].id - if drop["id"] == current_id: - campaigns[i].drops[j].update(drop["self"]) - # If after update we all conditions are meet we can claim the drop - if ( - campaigns[i].drops[j].is_claimable - is True - ): - claimed = self.claim_drop( - campaigns[i].drops[j] - ) - campaigns[i].drops[ - j - ].is_claimed = claimed - break # Found it! - campaigns[ - i - ].clear_drops() # Remove all the claime drops - break # Found it! + # Divide et impera :) + campaigns = self.__sync_campaigns(campaigns) - # Check if user It's currently streaming the same game present in campaigns_details - for index in range(0, len(streamers)): - if ( - streamers[index].settings.claim_drops is True - and streamers[index].is_online is True - and streamers[index].stream.drops_tags is True - ): - # yes! The streamer[index] have the drops_tags enabled and we It's currently stream a game with campaign active! - streamers[index].stream.drops_campaigns = list( - filter( - lambda x: x.drops != [] - and x.game == streamers[index].stream.game, - 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, ) - else: - logger.error("Error! Inventory is empty") - self.__check_connection_handler(chunk_size) + ) except (ValueError, KeyError, requests.exceptions.ConnectionError) as e: logger.error(f"Error while syncing inventory: {e}") @@ -410,35 +413,33 @@ class Twitch(object): ) and streamers[index].stream.minute_watched < 7 ): + """ logger.debug( f"Switch priority: {streamers[index]}, WatchStreak missing is {streamers[index].stream.watch_streak_missing} and minute_watched: {round(streamers[index].stream.minute_watched, 2)}" ) + """ streamers_watching.append(index) if len(streamers_watching) == 2: break elif prior == Priority.DROPS and len(streamers_watching) < 2: for index in streamers_index: - # For the truth we don't need al of this If - condition - # because the drops_campaigns can be fulled only if claim_drops is True and drops_tags is True - if ( - streamers[index].settings.claim_drops is True - and streamers[index].stream.drops_tags is True - and streamers[index].stream.drops_campaigns != [] - ): + if streamers[index].drops_condition() is True: + """ stream = streamers[index].stream drops_available = sum( [ len(campaign.drops) - for campaign in stream.drops_campaigns + for campaign in stream.campaigns ] ) logger.debug( f"{streamers[index]} it's currently stream: {stream}" ) logger.debug( - f"Campaign currently active here: {len(stream.drops_campaigns)}, drops available: {drops_available}" + f"Campaign currently active here: {len(stream.campaigns)}, drops available: {drops_available}" ) + """ streamers_watching.append(index) if len(streamers_watching) == 2: break @@ -470,7 +471,7 @@ class Twitch(object): 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.drops_campaigns: + for campaign in streamers[index].stream.campaigns: for drop in campaign.drops: # We could add .has_preconditions_met condition inside is_printable if ( diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index 5021572..fbb220f 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -50,3 +50,19 @@ class Campaign(object): 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 index d4d8b52..b975aaf 100644 --- a/TwitchChannelPointsMiner/classes/entities/Drop.py +++ b/TwitchChannelPointsMiner/classes/entities/Drop.py @@ -30,7 +30,7 @@ class Drop(object): ) self.minutes_required = dict["requiredMinutesWatched"] - self.has_preconditions_met = False + 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 diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index 477f0cd..ee70b61 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -16,7 +16,7 @@ class Stream(object): "game", "tags", "drops_tags", - "drops_campaigns", + "campaigns", "viewers_count", "__last_update", "spade_url", @@ -34,7 +34,8 @@ class Stream(object): self.tags = [] self.drops_tags = False - self.drops_campaigns = [] + self.campaigns = [] + self.campaigns_ids = [] self.viewers_count = 0 self.__last_update = 0 diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 103b007..845ee12 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -126,3 +126,11 @@ class Streamer(object): 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 != [] + ) diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 1ff05a7..34f38ac 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -120,3 +120,12 @@ class GQLOperations: } }, } + DropsHighlightService_AvailableDrops = { + "operationName": "DropsHighlightService_AvailableDrops", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "b19ee96a0e79e3f8281c4108bc4c7b3f232266db6f96fd04a339ab393673a075", + } + }, + } From 70e9b94d9dd7665b17ea3219fa64927425d1bd0c Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 18 Feb 2021 22:00:36 +0100 Subject: [PATCH 111/124] Order in Twitch.py - Create subgroups/categories with comment --- TwitchChannelPointsMiner/classes/Twitch.py | 558 +++++++++++---------- 1 file changed, 281 insertions(+), 277 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 20d21b7..c8d6c47 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -48,6 +48,7 @@ class Twitch(object): 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) @@ -81,22 +82,6 @@ class Twitch(object): {"event": "minute-watched", "properties": event_properties} ] - 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_spade_url(self, streamer): try: headers = {"User-Agent": self.user_agent} @@ -112,27 +97,6 @@ class Twitch(object): except requests.exceptions.RequestException as e: logger.error(f"Something went wrong during extraction of 'spade_url': {e}") - 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, - "User-Agent": self.user_agent, - }, - ) - 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 {} - def get_broadcast_id(self, streamer): json_data = copy.deepcopy(GQLOperations.WithIsStreamLiveQuery) json_data["variables"] = {"id": streamer.channel_id} @@ -172,135 +136,57 @@ class Twitch(object): except StreamerIsOfflineException: streamer.set_offline() - def claim_bonus(self, streamer, claim_id): - if Settings.logger.less is False: + def get_channel_id(self, streamer_username): + json_response = self.__do_helix_request(f"/users?login={streamer_username}") + if "data" not in json_response: + raise StreamerDoesNotExistException + else: + data = json_response["data"] + if len(data) >= 1: + return data[0]["id"] + else: + raise StreamerDoesNotExistException + + def get_followers(self, first=100): + followers = [] + pagination = {} + while 1: + query = f"/users/follows?from_id={self.twitch_login.get_user_id()}&first={first}" + if pagination != {}: + query += f"&after={pagination['cursor']}" + + json_response = self.__do_helix_request(query) + pagination = json_response["pagination"] + followers += [fw["to_login"].lower() for fw in json_response["data"]] + time.sleep(random.uniform(0.3, 0.7)) + + if pagination == {}: + break + + return followers + + 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"Claiming the bonus for {streamer}!", extra={"emoji": ":gift:"} + f"Joining raid from {streamer} to {raid.target_login}!", + extra={"emoji": ":performing_arts:"}, ) - json_data = copy.deepcopy(GQLOperations.ClaimCommunityPoints) - json_data["variables"] = { - "input": {"channelID": streamer.channel_id, "claimID": claim_id} - } - self.post_gql_request(json_data) - - def claim_drop(self, drop): - logger.info(f"Claim {drop}", extra={"emoji": ":package:"}) - - json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) - json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} + 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: - return response["data"]["claimDropRewards"]["status"] == "ELIGIBLE_FOR_ALL" + streamer.viewer_is_mod = response["data"]["user"]["self"]["isModerator"] except (ValueError, KeyError): - return False - - def claim_all_drops_from_inventory(self): - inventory = self.__get_inventory() - 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 __get_inventory(self): - response = self.post_gql_request(GQLOperations.Inventory) - return response["data"]["currentUser"]["inventory"] if response != {} else {} - - def __get_drops_dashboard(self, status=None): - 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)) - return campaigns - - def __get_campaigns_details(self, campaigns): - json_data = [] - for campaign in campaigns: - 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) - return list(map(lambda x: x["data"]["user"]["dropCampaign"], response)) - - 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"] is not 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 - ) - campaigns[i].clear_drops() # Remove all the claimed drops - break - return campaigns - - 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 - ): - campaigns_update = time.time() - # 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)): - 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) - - # 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) + 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 @@ -309,62 +195,44 @@ class Twitch(object): if self.running is False: break - # 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"] - - 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:"}, - ) - 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:"} - ) - logger.info( - f"Skip settings {event.bet.settings.filter_condition}, current value is: {compared_value}", - extra={"emoji": ":pushpin:"}, - ) - else: - if decision["amount"] >= 10: - logger.info( - f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", - extra={"emoji": ":four_leaf_clover:"}, - ) - - json_data = copy.deepcopy(GQLOperations.MakePrediction) - json_data["variables"] = { - "input": { - "eventID": event.event_id, - "outcomeID": decision["id"], - "points": decision["amount"], - "transactionID": token_hex(16), - } - } - return self.post_gql_request(json_data) - else: - logger.info( - f"Oh no! The event is not active anymore! Current status: {event.status}", - extra={"emoji": ":disappointed_relieved:"}, + 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 __do_helix_request(self, query, response_as_json=True): + url = f"{API}/helix/{query.strip('/')}" + response = self.twitch_login.session.get(url) + logger.debug( + f"Query: {query}, Status code: {response.status_code}, Content: {response.json()}" + ) + return response.json() if response_as_json is True else response + + 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, + "User-Agent": self.user_agent, + }, + ) + 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 {} def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: @@ -498,70 +366,206 @@ class Twitch(object): if streamers_watching == []: self.__chuncked_sleep(60, chunk_size=chunk_size) - 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 get_channel_id(self, streamer_username): - json_response = self.__do_helix_request(f"/users?login={streamer_username}") - if "data" not in json_response: - raise StreamerDoesNotExistException - else: - data = json_response["data"] - if len(data) >= 1: - return data[0]["id"] - else: - raise StreamerDoesNotExistException - - def get_followers(self, first=100): - followers = [] - pagination = {} - while 1: - query = f"/users/follows?from_id={self.twitch_login.get_user_id()}&first={first}" - if pagination != {}: - query += f"&after={pagination['cursor']}" - - json_response = self.__do_helix_request(query) - pagination = json_response["pagination"] - followers += [fw["to_login"].lower() for fw in json_response["data"]] - time.sleep(random.uniform(0.3, 0.7)) - - if pagination == {}: - break - - return followers - - def __do_helix_request(self, query, response_as_json=True): - url = f"{API}/helix/{query.strip('/')}" - response = self.twitch_login.session.get(url) - logger.debug( - f"Query: {query}, Status code: {response.status_code}, Content: {response.json()}" - ) - return response.json() if response_as_json is True else response - - 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:"}, - ) - - def viewer_is_mod(self, streamer): - json_data = copy.deepcopy(GQLOperations.ModViewChannelQuery) + # === 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"] + + 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:"}, + ) + 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:"} + ) + logger.info( + f"Skip settings {event.bet.settings.filter_condition}, current value is: {compared_value}", + extra={"emoji": ":pushpin:"}, + ) + else: + if decision["amount"] >= 10: + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) + + json_data = copy.deepcopy(GQLOperations.MakePrediction) + json_data["variables"] = { + "input": { + "eventID": event.event_id, + "outcomeID": decision["id"], + "points": decision["amount"], + "transactionID": token_hex(16), + } + } + return self.post_gql_request(json_data) + else: + logger.info( + f"Oh no! The event is not active anymore! Current status: {event.status}", + extra={"emoji": ":disappointed_relieved:"}, + ) + + def claim_bonus(self, streamer, claim_id): + if Settings.logger.less is False: + logger.info( + f"Claiming the bonus for {streamer}!", extra={"emoji": ":gift:"} + ) + + json_data = copy.deepcopy(GQLOperations.ClaimCommunityPoints) + json_data["variables"] = { + "input": {"channelID": streamer.channel_id, "claimID": claim_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: - streamer.viewer_is_mod = response["data"]["user"]["self"]["isModerator"] + return ( + [] + if response["data"]["channel"]["viewerDropCampaigns"] is None + else [ + item["id"] + for item in response["data"]["channel"]["viewerDropCampaigns"] + ] + ) except (ValueError, KeyError): - streamer.viewer_is_mod = False + return [] + + def __get_inventory(self): + response = self.post_gql_request(GQLOperations.Inventory) + return response["data"]["currentUser"]["inventory"] if response != {} else {} + + def __get_drops_dashboard(self, status=None): + 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)) + return campaigns + + def __get_campaigns_details(self, campaigns): + json_data = [] + for campaign in campaigns: + 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) + return list(map(lambda x: x["data"]["user"]["dropCampaign"], response)) + + 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"] is not 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 + ) + campaigns[i].clear_drops() # Remove all the claimed drops + break + return campaigns + + def claim_drop(self, drop): + logger.info(f"Claim {drop}", extra={"emoji": ":package:"}) + + json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) + json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} + response = self.post_gql_request(json_data) + try: + return response["data"]["claimDropRewards"]["status"] == "ELIGIBLE_FOR_ALL" + except (ValueError, KeyError): + return False + + def claim_all_drops_from_inventory(self): + inventory = self.__get_inventory() + 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 + ): + campaigns_update = time.time() + # 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)): + 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) + + # 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) From 14eb2023f240cf0f90fdc7dc9179b6ed94bab091 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 18 Feb 2021 22:02:43 +0100 Subject: [PATCH 112/124] Example and README.md update --- README.md | 8 +++++++- example.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a02955a..ffb8ed8 100644 --- a/README.md +++ b/README.md @@ -172,12 +172,18 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner import logging from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings +from TwitchChannelPointsMiner.classes.Settings import Priority from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup + priority=[ # Custom priority in this case for example: + Priority.DROPS, # - we want first of all to collect all drops + Priority.STREAK, # - after all drops are collected do priority on watch-streak + Priority.ORDER # - when we have all of drops claimed and no watch-streak avaialable use the order priority + ], logger_settings=LoggerSettings( save=True, # If you want to save logs in file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info) @@ -198,7 +204,7 @@ twitch_miner = TwitchChannelPointsMiner( stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 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 + where=Condition.LTE, # The key must be [GT, LT, GTE, LTE] than value value=800 ) ) diff --git a/example.py b/example.py index be6dc22..459d27f 100644 --- a/example.py +++ b/example.py @@ -3,12 +3,18 @@ import logging from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings +from TwitchChannelPointsMiner.classes.Settings import Priority from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup + priority=[ # Custom priority in this case for example: + Priority.DROPS, # - we want first of all to collect all drops + Priority.STREAK, # - after all drops are collected do priority on watch-streak + Priority.ORDER # - when we have all of drops claimed and no watch-streak avaialable use the order priority + ], logger_settings=LoggerSettings( save=True, # If you want to save logs in file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info) From a37d0e94b42d630e258c254748aa07f9f64d100e Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 18 Feb 2021 22:05:45 +0100 Subject: [PATCH 113/124] allowed channels array, saved but not used -- other -- --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 2 +- TwitchChannelPointsMiner/classes/entities/Campaign.py | 6 ++++++ TwitchChannelPointsMiner/classes/entities/Streamer.py | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index ae24059..9d9e577 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -200,7 +200,7 @@ class TwitchChannelPointsMiner: target=self.twitch.sync_campaigns, args=(self.streamers,), ) - self.sync_campaigns_thread.name = "Sync drops inventory" + self.sync_campaigns_thread.name = "Sync campaigns/inventory" self.sync_campaigns_thread.start() time.sleep(30) diff --git a/TwitchChannelPointsMiner/classes/entities/Campaign.py b/TwitchChannelPointsMiner/classes/entities/Campaign.py index fbb220f..17f9ace 100644 --- a/TwitchChannelPointsMiner/classes/entities/Campaign.py +++ b/TwitchChannelPointsMiner/classes/entities/Campaign.py @@ -15,6 +15,7 @@ class Campaign(object): "start_at", "dt_match", "drops", + "channels", ] def __init__(self, dict): @@ -22,6 +23,11 @@ class Campaign(object): 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") diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index 845ee12..38ab5fe 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -63,8 +63,8 @@ class Streamer(object): ] def __init__(self, username, settings=None): - self.username = username.lower().strip() - self.channel_id = 0 + self.username: str = username.lower().strip() + self.channel_id: str = "" self.settings = settings self.is_online = False self.stream_up = 0 From 16fe9ed3afc4e1df3d7d39c6d054503d7c470bad Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 18 Feb 2021 22:15:50 +0100 Subject: [PATCH 114/124] Just a little fix, currently in testing - No difference, only good code :rofl: --- TwitchChannelPointsMiner/classes/entities/Stream.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index ee70b61..99b378f 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -17,12 +17,13 @@ class Stream(object): "tags", "drops_tags", "campaigns", + "campaigns_ids", "viewers_count", - "__last_update", "spade_url", "payload", "watch_streak_missing", "minute_watched", + "__last_update", "__minute_watched_timestamp", ] From e36611bb6174d9aee98882c615795d464bae3091 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Mon, 22 Feb 2021 12:00:58 +0100 Subject: [PATCH 115/124] Handling empty inventory. #84 --- TwitchChannelPointsMiner/classes/Twitch.py | 31 +++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c8d6c47..04c97ac 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -455,7 +455,12 @@ class Twitch(object): def __get_inventory(self): response = self.post_gql_request(GQLOperations.Inventory) - return response["data"]["currentUser"]["inventory"] if response != {} else {} + 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) @@ -480,10 +485,10 @@ class Twitch(object): # 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"] is not None - ): + 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)): @@ -512,13 +517,15 @@ class Twitch(object): def claim_all_drops_from_inventory(self): inventory = self.__get_inventory() - 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)) + 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 From 8594a39e4169e5eb76cc1a3b7f5d4f668da150fe Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 23 Feb 2021 15:58:45 +0100 Subject: [PATCH 116/124] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcd4363..d442052 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Twitch Channel Points Miner - v2](./assets/banner.png)

License -Python3 +Python3 PRsWelcome GitHub Repo stars GitHub closed issues From 0088f41c57b4413ec15d74f69d8075db68593990 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 23 Feb 2021 15:59:39 +0100 Subject: [PATCH 117/124] Update setup.py --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index bdbd898..c9d80da 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,6 @@ setuptools.setup( long_description_content_type="text/markdown", classifiers=[ "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -45,5 +43,5 @@ setuptools.setup( "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: English", ], - python_requires=">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", ) From eb665c3032f7572802766705ffe2fdb27749e1b9 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Tue, 23 Feb 2021 18:15:23 +0100 Subject: [PATCH 118/124] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d442052..080538c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Twitch Channel Points Miner - v2](./assets/banner.png)

License -Python3 +Python3 PRsWelcome GitHub Repo stars GitHub closed issues From 80c37c58dbac8e09b1e6f903fe0690f220786486 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 24 Feb 2021 09:53:33 +0100 Subject: [PATCH 119/124] Default priority STREAK, DROPS, ORDER --- README.md | 6 +++--- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 2 +- example.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3186a9d..6191a31 100644 --- a/README.md +++ b/README.md @@ -176,8 +176,8 @@ twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup priority=[ # Custom priority in this case for example: - Priority.DROPS, # - we want first of all to collect all drops - Priority.STREAK, # - after all drops are collected do priority on watch-streak + 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 drops claimed and no watch-streak avaialable use the order priority ], logger_settings=LoggerSettings( @@ -200,7 +200,7 @@ twitch_miner = TwitchChannelPointsMiner( stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 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, # The key must be [GT, LT, GTE, LTE] than value + where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value value=800 ) ) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 15386d6..5fd1d0e 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -61,7 +61,7 @@ class TwitchChannelPointsMiner: self, username: str, claim_drops_startup: bool = False, - priority=[Priority.DROPS, Priority.STREAK, Priority.ORDER], + priority=[Priority.STREAK, Priority.DROPS, Priority.ORDER], # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), # Default values for all streamers diff --git a/example.py b/example.py index 19bc0f3..ea1e836 100644 --- a/example.py +++ b/example.py @@ -11,8 +11,8 @@ twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", claim_drops_startup=False, # If you want to auto claim all drops from Twitch inventory on startup priority=[ # Custom priority in this case for example: - Priority.DROPS, # - we want first of all to collect all drops - Priority.STREAK, # - after all drops are collected do priority on watch-streak + 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 drops claimed and no watch-streak avaialable use the order priority ], logger_settings=LoggerSettings( From 189e60a4aad766ae3c0926288fd4ae51cfc82cf1 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 24 Feb 2021 10:19:26 +0100 Subject: [PATCH 120/124] Some users don't understand that the password input is hidden. For prevent future issue with the same refence enable password from code. Ref #89, Close #77 --- README.md | 1 + TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 5 +++-- TwitchChannelPointsMiner/classes/Twitch.py | 6 ++++-- TwitchChannelPointsMiner/classes/TwitchLogin.py | 10 ++++++++-- example.py | 1 + 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 64201c4..8e3e5a8 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, Streame 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 startup priority=[ # Custom priority in this case for example: Priority.STREAK, # - we want first of all to catch all watch streak from all streamers diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 5fd1d0e..d64caad 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -60,8 +60,9 @@ class TwitchChannelPointsMiner: def __init__( self, username: str, + password: str = None, claim_drops_startup: bool = False, - priority=[Priority.STREAK, Priority.DROPS, Priority.ORDER], + 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 @@ -78,7 +79,7 @@ class TwitchChannelPointsMiner: Settings.streamer_settings = streamer_settings user_agent = get_user_agent("FIREFOX") - self.twitch = Twitch(self.username, user_agent) + self.twitch = Twitch(self.username, user_agent, password) self.claim_drops_startup = claim_drops_startup self.priority = priority diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 04c97ac..2b22a3e 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -32,12 +32,14 @@ logger = logging.getLogger(__name__) class Twitch(object): __slots__ = ["cookies_file", "user_agent", "twitch_login", "running"] - def __init__(self, username, user_agent): + 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.twitch_login = TwitchLogin(CLIENT_ID, username, self.user_agent) + self.twitch_login = TwitchLogin( + CLIENT_ID, username, self.user_agent, password=password + ) self.running = True def login(self): diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 7c15850..563a4c6 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -23,12 +23,13 @@ class TwitchLogin(object): "session", "session", "username", + "password", "user_id", "email", "cookies", ] - def __init__(self, client_id, username, user_agent): + def __init__(self, client_id, username, user_agent, password=None): self.client_id = client_id self.token = None self.login_check_result = False @@ -37,6 +38,7 @@ class TwitchLogin(object): {"Client-ID": self.client_id, "User-Agent": user_agent} ) self.username = username + self.password = password self.user_id = None self.email = None @@ -55,7 +57,11 @@ class TwitchLogin(object): while True: # self.username = input('Enter Twitch username: ') - password = getpass.getpass(f"Enter Twitch password for {self.username}: ") + password = ( + getpass.getpass(f"Enter Twitch password for {self.username}: ") + if self.password is None + else self.password + ) post_data["username"] = self.username post_data["password"] = password diff --git a/example.py b/example.py index ea1e836..0a32477 100644 --- a/example.py +++ b/example.py @@ -9,6 +9,7 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, Streame 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 startup priority=[ # Custom priority in this case for example: Priority.STREAK, # - we want first of all to catch all watch streak from all streamers From 017c87d1c9d3174a324ac074143230e69c3b6111 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 24 Feb 2021 22:53:25 +0100 Subject: [PATCH 121/124] Create 2 priority (POINTS_ASCENDING, POINTS_DESCEDING) - Close #87 --- README.md | 11 ++++- .../TwitchChannelPointsMiner.py | 2 +- TwitchChannelPointsMiner/classes/Settings.py | 2 + TwitchChannelPointsMiner/classes/Twitch.py | 40 +++++++------------ example.py | 2 +- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 8e3e5a8..1c08d7c 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ twitch_miner = TwitchChannelPointsMiner( 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 drops claimed and no watch-streak avaialable use the order priority + Priority.ORDER # - when we have all of drops claimed and no watch-streak avaialable use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) @@ -249,6 +249,15 @@ twitch_miner.mine(["streamer1", "streamer2"], followers=True) # Mixed Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` Twitch return the streamers order by followed_at. So your last follow has the highest priority. ## Settings +Most of the settings are self-explained and are commented in example. +You can watch only two streamers per time. With `priority` settings you can select which streamers to priority. You can use an array of priority on single items. +Available values are the following: + - `STREAK` - Catch the watch streak from all streamers + - `DROPS` - Claim all drops from streamers with drops tags enabled + - `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 | diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index d64caad..ad92437 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -82,7 +82,7 @@ class TwitchChannelPointsMiner: self.twitch = Twitch(self.username, user_agent, password) self.claim_drops_startup = claim_drops_startup - self.priority = priority + self.priority = priority if isinstance(priority, list) else [priority] self.streamers = [] self.events_predictions = {} diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index d610c84..05c6479 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -5,6 +5,8 @@ class Priority(Enum): ORDER = auto() STREAK = auto() DROPS = auto() + POINTS_ASCENDING = auto() + POINTS_DESCEDING = auto() # Empty object shared between class diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2b22a3e..f22efe1 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -238,11 +238,6 @@ class Twitch(object): def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: - # OK! We will do the following: - # - Create an array of int - index of streamers currently online - # - Create a dictionary with grouped streamers, based on watch-streak or drops - # - For each array we don't need more than 2 streamer (becuase we can't watch more than 2) - streamers_index = [ i for i in range(0, len(streamers)) @@ -265,6 +260,21 @@ class Twitch(object): # 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 @@ -283,11 +293,6 @@ class Twitch(object): ) and streamers[index].stream.minute_watched < 7 ): - """ - logger.debug( - f"Switch priority: {streamers[index]}, WatchStreak missing is {streamers[index].stream.watch_streak_missing} and minute_watched: {round(streamers[index].stream.minute_watched, 2)}" - ) - """ streamers_watching.append(index) if len(streamers_watching) == 2: break @@ -295,21 +300,6 @@ class Twitch(object): elif prior == Priority.DROPS and len(streamers_watching) < 2: for index in streamers_index: if streamers[index].drops_condition() is True: - """ - stream = streamers[index].stream - drops_available = sum( - [ - len(campaign.drops) - for campaign in stream.campaigns - ] - ) - logger.debug( - f"{streamers[index]} it's currently stream: {stream}" - ) - logger.debug( - f"Campaign currently active here: {len(stream.campaigns)}, drops available: {drops_available}" - ) - """ streamers_watching.append(index) if len(streamers_watching) == 2: break diff --git a/example.py b/example.py index 0a32477..a574194 100644 --- a/example.py +++ b/example.py @@ -14,7 +14,7 @@ twitch_miner = TwitchChannelPointsMiner( 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 drops claimed and no watch-streak avaialable use the order priority + Priority.ORDER # - when we have all of drops claimed and no watch-streak avaialable use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) From 9052cd7ee334208914e72f5d8f6ae56fa9ab3262 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Wed, 24 Feb 2021 23:06:12 +0100 Subject: [PATCH 122/124] Timeout in request and handling Exception. Fix #91 --- TwitchChannelPointsMiner/classes/Twitch.py | 227 +++++++++++---------- 1 file changed, 119 insertions(+), 108 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index f22efe1..d651c72 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -238,125 +238,136 @@ class Twitch(object): def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: - 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), + 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 ) - 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 - ): - streamers_watching.append(index) - if len(streamers_watching) == 2: - break + 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]) - 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 + 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] - """ - 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}, - ) - 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() + 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: """ - 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. + 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 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 - ): - # print("=" * 125) - logger.info( - f"{streamers[index]} is streaming {streamers[index].stream}" + 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 ) - logger.info(f"Campaign: {campaign}") - logger.info(f"Drop: {drop}") - logger.info(f"{drop.progress_bar()}") - # print("=" * 125) + > 30 + ) + and streamers[index].stream.minute_watched < 7 + ): + streamers_watching.append(index) + if len(streamers_watching) == 2: + break - except requests.exceptions.ConnectionError as e: - logger.error(f"Error while trying to send minute watched: {e}") - self.__check_connection_handler(chunk_size) + 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 - self.__chuncked_sleep( - next_iteration - time.time(), chunk_size=chunk_size - ) + """ + 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] - if streamers_watching == []: - self.__chuncked_sleep(60, chunk_size=chunk_size) + 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 + ): + # print("=" * 125) + logger.info( + f"{streamers[index]} is streaming {streamers[index].stream}" + ) + logger.info(f"Campaign: {campaign}") + logger.info(f"Drop: {drop}") + logger.info(f"{drop.progress_bar()}") + # print("=" * 125) + + 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 From 6de210b496dbc464f92cbff1f2f36de765e14957 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Thu, 25 Feb 2021 11:09:36 +0100 Subject: [PATCH 123/124] Choose gained/lost in the bet results. Close #93 --- README.md | 13 +++++++------ TwitchChannelPointsMiner/classes/WebSocketsPool.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1c08d7c..104c005 100644 --- a/README.md +++ b/README.md @@ -250,13 +250,14 @@ Make sure to write the streamers array in order of priority from left to right. ## Settings Most of the settings are self-explained and are commented in example. -You can watch only two streamers per time. With `priority` settings you can select which streamers to priority. You can use an array of priority on single items. +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 to use 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 - - `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 + - `STREAK` - Catch the watch streak from all streamers + - `DROPS` - Claim all drops from streamers with drops tags enabled + - `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 diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 72eb267..40620a9 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -308,8 +308,17 @@ class WebSocketsPool: else 0 ) points_prefix = "+" if points_gained >= 0 else "" + action = ( + "Lost" + if result_type == "LOSE" + else ( + "Refunded" + if result_type == "REFUND" + else "Gained" + ) + ) logger.info( - f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {points_prefix}{_millify(points_gained)}", + f"{ws.events_predictions[event_id]} - Result: {result_type}, {action}: {points_prefix}{_millify(points_gained)}", extra={"emoji": ":bar_chart:"}, ) ws.events_predictions[event_id].final_result = { From be39a152244bf70e8a8cac1990927de80dcf3f29 Mon Sep 17 00:00:00 2001 From: Alessandro Maggio Date: Fri, 26 Feb 2021 10:53:56 +0100 Subject: [PATCH 124/124] A setting that will allow to blacklist streamer from farming points to him. Close #94 --- README.md | 16 ++++++++++++++-- .../TwitchChannelPointsMiner.py | 13 +++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 104c005..e56c09f 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ If you have any type of issue, you need help, or you just want to suggest a new If you want to help on this project, please leave a star 🌟 and share it with your friends! 😎 -A coffee is always a sign of LOVE ❤️ +A coffee is always a gesture of LOVE ❤️ Buy Me A Coffee @@ -160,7 +160,13 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner ## How to use: 1. Clone this repository `git clone https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2` -2. Install all the requirements `pip install -r requirements.txt` +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 +``` 3. Create your `run.py` file start from [example.py](/example.py) ```python # -*- coding: utf-8 -*- @@ -241,6 +247,12 @@ twitch_miner.mine(["streamer1", "streamer2"]) # Array of strea twitch_miner.mine(followers=True) # Automatic use the followers list OR twitch_miner.mine(["streamer1", "streamer2"], followers=True) # 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 `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"]) # Automatic use the followers list OR +``` 4. Start mining! `python run.py` ### Limits diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index ad92437..0aa8ef9 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -100,10 +100,10 @@ class TwitchChannelPointsMiner: for sign in [signal.SIGINT, signal.SIGSEGV, signal.SIGTERM]: signal.signal(sign, self.end) - def mine(self, streamers: list = [], followers=False): - self.run(streamers, followers) + def mine(self, streamers: list = [], blacklist: list = [], followers=False): + self.run(streamers=streamers, blacklist=blacklist, followers=followers) - def run(self, streamers: list = [], followers=False): + def run(self, streamers: list = [], blacklist: list = [], followers=False): if self.running: logger.error("You can't start multiple sessions of this instance!") else: @@ -127,8 +127,9 @@ class TwitchChannelPointsMiner: if isinstance(streamer, Streamer) else streamer.lower().strip() ) - streamers_name.append(username) - streamers_dict[username] = streamer + if username not in blacklist: + streamers_name.append(username) + streamers_dict[username] = streamer if followers is True: followers_array = self.twitch.get_followers() @@ -137,7 +138,7 @@ class TwitchChannelPointsMiner: extra={"emoji": ":clipboard:"}, ) for username in followers_array: - if username not in streamers_dict: + if username not in streamers_dict and username not in blacklist: streamers_dict[username] = username.lower().strip() else: followers_array = []