From 125a08aceccd3e15bdb1f3ffff0bed8b11ed5dbb Mon Sep 17 00:00:00 2001 From: 1v Date: Thu, 6 May 2021 16:51:53 +0200 Subject: [PATCH 001/140] Add SMART_HIGH_ODDS strategy --- README.md | 4 ++ .../classes/entities/Bet.py | 44 +++++++++++++++++-- example.py | 1 + 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 588813e7..b17d8d5d 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ twitch_miner = TwitchChannelPointsMiner( 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) + target_odd=3, # Target odd for SMART_HIGH_ODDS strategy max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 filter_condition=FilterCondition( @@ -346,6 +347,7 @@ ColorPalette( | `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) | +| `target_odd` | float | 3 | Target odd for SMART_HIGH_ODDS strategy | | `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) | | `join_chat` | bool | True | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | @@ -354,6 +356,7 @@ ColorPalette( - **MOST_VOTED**: Select the option most voted based on users count - **HIGH_ODDS**: Select the option with the highest odds +- **SMART_HIGH_ODDS**: Select the option with the highest odds if odd is not less than `target_odd`. Also prevents high bets with very high 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 select the option with the highest odds @@ -363,6 +366,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' +- **SMART_HIGH_ODDS**: No bet because highest odd 2.27 is lower than default target odd (3) - **PERCENTAGE**: The highest percentage is 56% for **'under 7.5'** - **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index cffde0d7..5c727367 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -10,6 +10,7 @@ class Strategy(Enum): MOST_VOTED = auto() HIGH_ODDS = auto() + SMART_HIGH_ODDS = auto() PERCENTAGE = auto() SMART = auto() @@ -63,6 +64,7 @@ class BetSettings(object): "percentage", "percentage_gap", "max_points", + "target_odd", "stealth_mode", "filter_condition", ] @@ -73,6 +75,7 @@ def __init__( percentage: int = None, percentage_gap: int = None, max_points: int = None, + target_odd: float = None, stealth_mode: bool = None, filter_condition: FilterCondition = None, ): @@ -80,6 +83,7 @@ def __init__( self.percentage = percentage self.percentage_gap = percentage_gap self.max_points = max_points + self.target_odd = target_odd self.stealth_mode = stealth_mode self.filter_condition = filter_condition @@ -88,6 +92,7 @@ def default(self): self.percentage = self.percentage if not None else 5 self.percentage_gap = self.percentage_gap if not None else 20 self.max_points = self.max_points if not None else 50000 + self.target_odd = self.target_odd if not None else 3 self.stealth_mode = self.stealth_mode if not None else False def __repr__(self): @@ -214,6 +219,16 @@ def skip(self) -> bool: outcome_index = char_decision_as_index(self.decision["choice"]) compared_value = self.outcomes[outcome_index][fixed_key] + if ( + self.settings.strategy == Strategy.SMART_HIGH_ODDS + and self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + ): + output = f"Odd is too low.\n" + output += f"{self.get_outcome(0)}\n{self.get_outcome(1)}\n" + output += f"Target odd: {self.settings.target_odd}" + return True, output # Skip + # Check if condition is satisfied if condition == Condition.GT: if compared_value > value: @@ -231,12 +246,26 @@ def skip(self) -> bool: else: return False, 0 # Default don't skip the bet + def calculate_sho_bet(self, index): + low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] + high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + if high_odd_points <= 50: # in case no one bet + return 50 + else: + target_odd = self.settings.target_odd + if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): + # don't bet too much if odds is too high + target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 + return int((low_odd_points / (target_odd - 1)) - high_odd_points) + def calculate(self, balance: int) -> dict: self.decision = {"choice": None, "amount": 0, "id": None} if self.settings.strategy == Strategy.MOST_VOTED: self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS) elif self.settings.strategy == Strategy.HIGH_ODDS: self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) + elif self.settings.strategy == Strategy.SMART_HIGH_ODDS: + self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) elif self.settings.strategy == Strategy.PERCENTAGE: self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS_PERCENTAGE) elif self.settings.strategy == Strategy.SMART: @@ -253,10 +282,17 @@ def calculate(self, balance: int) -> dict: if self.decision["choice"] is not None: 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.strategy == Strategy.SMART_HIGH_ODDS: + self.decision["amount"] = min( + self.calculate_sho_bet(index), + int(balance * (self.settings.percentage / 100)), + self.settings.max_points, + ) + else: + 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"] diff --git a/example.py b/example.py index a8eb20f3..72ced677 100644 --- a/example.py +++ b/example.py @@ -40,6 +40,7 @@ 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) + target_odd=3, # Target odd for SMART_HIGH_ODDS strategy max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33 filter_condition=FilterCondition( From 8c47c437d1e6e272407537120935a6308da809c6 Mon Sep 17 00:00:00 2001 From: 1v Date: Mon, 21 Jun 2021 12:15:23 +0200 Subject: [PATCH 002/140] Add only_doubt option and improve SMART_HIGH_ODDS strategy --- .../classes/entities/Bet.py | 45 ++++++++++++++----- example.py | 1 + 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 5c727367..81d0cc7c 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -65,6 +65,7 @@ class BetSettings(object): "percentage_gap", "max_points", "target_odd", + "only_doubt", "stealth_mode", "filter_condition", ] @@ -76,6 +77,7 @@ def __init__( percentage_gap: int = None, max_points: int = None, target_odd: float = None, + only_doubt: bool = None, stealth_mode: bool = None, filter_condition: FilterCondition = None, ): @@ -84,6 +86,7 @@ def __init__( self.percentage_gap = percentage_gap self.max_points = max_points self.target_odd = target_odd + self.only_doubt = only_doubt self.stealth_mode = stealth_mode self.filter_condition = filter_condition @@ -93,6 +96,7 @@ def default(self): self.percentage_gap = self.percentage_gap if not None else 20 self.max_points = self.max_points if not None else 50000 self.target_odd = self.target_odd if not None else 3 + self.only_doubt = self.only_doubt if not None else False self.stealth_mode = self.stealth_mode if not None else False def __repr__(self): @@ -219,15 +223,34 @@ def skip(self) -> bool: outcome_index = char_decision_as_index(self.decision["choice"]) compared_value = self.outcomes[outcome_index][fixed_key] - if ( - self.settings.strategy == Strategy.SMART_HIGH_ODDS - and self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - ): - output = f"Odd is too low.\n" - output += f"{self.get_outcome(0)}\n{self.get_outcome(1)}\n" - output += f"Target odd: {self.settings.target_odd}" - return True, output # Skip + if self.settings.strategy == Strategy.SMART_HIGH_ODDS: + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 + ): + return False, "No bet on B." + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + ): + if not self.settings.only_doubt: + return False, "No bet on A." + both_odds_too_low = ( + self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + ) + is_only_doubt = ( + self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.settings.only_doubt + ) + if both_odds_too_low: + output = "Odd is too low.\n" + elif is_only_doubt: + output = "Odd is too low and only_doubt activated.\n" + if both_odds_too_low or is_only_doubt: + output += f"{self.get_outcome(0)}\n{self.get_outcome(1)}\n" + output += f"Target odd: {self.settings.target_odd}" + return True, output # Skip # Check if condition is satisfied if condition == Condition.GT: @@ -260,7 +283,9 @@ def calculate_sho_bet(self, index): def calculate(self, balance: int) -> dict: self.decision = {"choice": None, "amount": 0, "id": None} - if self.settings.strategy == Strategy.MOST_VOTED: + if self.settings.only_doubt: + self.decision["choice"] = "B" + elif self.settings.strategy == Strategy.MOST_VOTED: self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS) elif self.settings.strategy == Strategy.HIGH_ODDS: self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) diff --git a/example.py b/example.py index 72ced677..3d22cf47 100644 --- a/example.py +++ b/example.py @@ -41,6 +41,7 @@ percentage=5, # Place the x% of your channel points percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy) target_odd=3, # Target odd for SMART_HIGH_ODDS strategy + only_doubt=False, # Will only doubt (bet on B). If set to True will overwrite strategy bet decision 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( From 9479ab5329bf140a4587d7e655d2e65700818b77 Mon Sep 17 00:00:00 2001 From: 1v Date: Thu, 30 Sep 2021 18:10:51 +0000 Subject: [PATCH 003/140] Add always_bet option --- .../classes/entities/Bet.py | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 81d0cc7c..31f6e66e 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -1,11 +1,14 @@ import copy from enum import Enum, auto from random import uniform +import logging from millify import millify from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round +from TwitchChannelPointsMiner.classes.Settings import Settings +logger = logging.getLogger(__name__) class Strategy(Enum): MOST_VOTED = auto() @@ -66,6 +69,7 @@ class BetSettings(object): "max_points", "target_odd", "only_doubt", + "always_bet", "stealth_mode", "filter_condition", ] @@ -78,6 +82,7 @@ def __init__( max_points: int = None, target_odd: float = None, only_doubt: bool = None, + always_bet: bool = None, stealth_mode: bool = None, filter_condition: FilterCondition = None, ): @@ -87,6 +92,7 @@ def __init__( self.max_points = max_points self.target_odd = target_odd self.only_doubt = only_doubt + self.always_bet = always_bet self.stealth_mode = stealth_mode self.filter_condition = filter_condition @@ -97,6 +103,7 @@ def default(self): self.max_points = self.max_points if not None else 50000 self.target_odd = self.target_odd if not None else 3 self.only_doubt = self.only_doubt if not None else False + self.always_bet = self.always_bet if not None else True self.stealth_mode = self.stealth_mode if not None else False def __repr__(self): @@ -203,6 +210,27 @@ def __clear_outcomes(self): def __return_choice(self, key) -> str: return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B" + def __both_odds_too_low(self) -> bool: + return ( + self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + ) + + def __is_only_doubt(self) -> bool: + return ( + self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.settings.only_doubt + ) + + def __log_skip(self, string) -> str: + logger.info( + string, + extra={ + "emoji": ":pushpin:", + "color": Settings.logger.color_palette.BET_GENERAL, + }, + ) + def skip(self) -> bool: if self.settings.filter_condition is not None: # key == by , condition == where @@ -228,29 +256,29 @@ def skip(self) -> bool: self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 ): - return False, "No bet on B." + self.__log_skip("No bet on B") + return False, 0 if ( self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 ): if not self.settings.only_doubt: - return False, "No bet on A." - both_odds_too_low = ( - self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - ) - is_only_doubt = ( - self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.settings.only_doubt - ) - if both_odds_too_low: - output = "Odd is too low.\n" - elif is_only_doubt: - output = "Odd is too low and only_doubt activated.\n" - if both_odds_too_low or is_only_doubt: - output += f"{self.get_outcome(0)}\n{self.get_outcome(1)}\n" - output += f"Target odd: {self.settings.target_odd}" - return True, output # Skip + self.__log_skip("No bet on A") + return False, 0 + + if self.__both_odds_too_low() or self.__is_only_doubt(): + if self.__both_odds_too_low(): + self.__log_skip("Odd is too low") + elif self.__is_only_doubt(): + self.__log_skip("Odd is too low and only_doubt activated") + self.__log_skip(f"{self.get_outcome(0)}; {self.get_outcome(1)}") + self.__log_skip(f"Target odd: {self.settings.target_odd}") + if self.settings.always_bet: + self.__log_skip("Odd is too low, but always_bet activated") + if self.settings.always_bet: + return False, 0 + else: + return True, 0 # Skip # Check if condition is satisfied if condition == Condition.GT: @@ -272,7 +300,9 @@ def skip(self) -> bool: def calculate_sho_bet(self, index): low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - if high_odd_points <= 50: # in case no one bet + if self.__both_odds_too_low() or self.__is_only_doubt(): + return 10 + elif high_odd_points <= 50: # in case no one bet return 50 else: target_odd = self.settings.target_odd From c77e78a4b3439325a67cd90aa9221196ff093b0b Mon Sep 17 00:00:00 2001 From: 1v Date: Fri, 8 Oct 2021 20:49:51 +0000 Subject: [PATCH 004/140] Refactor Bet class * Move code from skip() and calculate() methods to Strategy class * Add pytest * Add tests --- .../classes/entities/Bet.py | 360 ++++++++++-------- conftest.py | 0 pytest.ini | 2 + tests/test_bet.py | 206 ++++++++++ 4 files changed, 411 insertions(+), 157 deletions(-) create mode 100644 conftest.py create mode 100644 pytest.ini create mode 100644 tests/test_bet.py diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 5e1a31e4..15001e7b 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -1,26 +1,219 @@ import copy +import logging from enum import Enum, auto from random import uniform -import logging from millify import millify -from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round logger = logging.getLogger(__name__) -class Strategy(Enum): - MOST_VOTED = auto() - HIGH_ODDS = auto() - SMART_HIGH_ODDS = auto() - PERCENTAGE = auto() - SMART = auto() + +class Strategy(object): + MOST_VOTED = "MostVoted" + HIGH_ODDS = "HighOdds" + SMART_HIGH_ODDS = "SmartHighOdds" + PERCENTAGE = "Percentage" + SMART = "Smart" + + def __init__(self, outcomes: list, settings: object): + self.outcomes = outcomes + self.decision: dict = {} + self.settings = settings + + def get_instance(self): + subclass = globals()[self.settings.strategy] + return subclass(self.outcomes, self.settings) + + def return_choice(self, key) -> str: + return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B" + + def calculate_before(self): + self.decision = {"choice": None, "amount": 0, "id": None} + + def calculate_middle(self): + pass + + def calculate_after(self, balance: int): + if self.settings.only_doubt: + self.decision["choice"] = "B" + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["id"] = self.outcomes[index]["id"] + amounts = [ + self.decision["amount"], + int(balance * (self.settings.percentage / 100)), + self.settings.max_points, + ] + self.decision["amount"] = min(x for x in amounts if x != 0) + if ( + self.settings.stealth_mode is True + and self.decision["amount"] + >= self.outcomes[index][OutcomeKeys.TOP_POINTS] + ): + reduce_amount = uniform(1, 5) + self.decision["amount"] = ( + self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount + ) + self.decision["amount"] = int(self.decision["amount"]) + + def calculate(self, balance: int) -> dict: + self.calculate_before() + self.calculate_middle() + self.calculate_after(balance) + return self.decision + + def skip_before(self): + if self.settings.always_bet: + self.log_skip("Odd is too low, but always_bet activated") + return False, 0 + + def skip_middle(self): + pass + + def skip_after(self): + if self.settings.filter_condition is not None: + self.decision.setdefault("choice", None) # for tests + # key == by , condition == where + key = self.settings.filter_condition.by + condition = self.settings.filter_condition.where + value = self.settings.filter_condition.value + + fixed_key = ( + key + if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS] + else key.replace("decision", "total") + ) + if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]: + compared_value = ( + self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] + ) + else: + outcome_index = char_decision_as_index(self.decision["choice"]) + compared_value = self.outcomes[outcome_index][fixed_key] + + # Check if condition is satisfied + if condition == Condition.GT: + if compared_value > value: + return False, compared_value + elif condition == Condition.LT: + if compared_value < value: + return False, compared_value + elif condition == Condition.GTE: + if compared_value >= value: + return False, compared_value + elif condition == Condition.LTE: + if compared_value <= value: + return False, compared_value + return True, compared_value # Else skip the bet + else: + return False, 0 # Default don't skip the bet + + def skip(self) -> bool: + skip_results = [self.skip_before(), self.skip_middle(), self.skip_after()] + return next(item for item in skip_results if item is not None) def __str__(self): return self.name +class SmartHighOdds(Strategy): + def both_odds_too_low(self) -> bool: + return ( + self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + ) + + def is_only_doubt(self) -> bool: + return ( + self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd + and self.settings.only_doubt + ) + + def log_skip(self, string) -> str: + logger.info( + string, + extra={ + "emoji": ":pushpin:", + "color": Settings.logger.color_palette.BET_GENERAL, + }, + ) + + def skip_middle(self): + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 + ): + self.log_skip("No bet on B") + return False, 0 + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + ): + if not self.settings.only_doubt: + self.log_skip("No bet on A") + return False, 0 + + if self.both_odds_too_low() or self.is_only_doubt(): + if self.both_odds_too_low(): + self.log_skip("Odd is too low") + elif self.is_only_doubt(): + self.log_skip("Odd is too low and only_doubt activated") + self.log_skip(f"{self.get_outcome(0)}; {self.get_outcome(1)}") + self.log_skip(f"Target odd: {self.settings.target_odd}") + return True, 0 # Skip + + def calculate_sho_bet(self, index): + low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] + high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + if self.both_odds_too_low() or self.is_only_doubt(): + return 10 + elif high_odd_points <= 50: # in case no one bet + return 50 + else: + target_odd = self.settings.target_odd + if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): + # don't bet too much if odds is too high + target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 + return int((low_odd_points / (target_odd - 1)) - high_odd_points) + + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["amount"] = int(self.calculate_sho_bet(index)) + + +class MostVoted(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) + + +class HighOdds(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + + +class Percentage(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) + + +class Smart(Strategy): + def calculate_middle(self): + difference = abs( + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] + ) + self.decision["choice"] = ( + self.return_choice(OutcomeKeys.ODDS) + if difference < self.settings.percentage_gap + else self.return_choice(OutcomeKeys.TOTAL_USERS) + ) + + class Condition(Enum): GT = auto() LT = auto() @@ -228,155 +421,8 @@ def __clear_outcomes(self): 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 __both_odds_too_low(self) -> bool: - return ( - self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - ) - - def __is_only_doubt(self) -> bool: - return ( - self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.settings.only_doubt - ) - - def __log_skip(self, string) -> str: - logger.info( - string, - extra={ - "emoji": ":pushpin:", - "color": Settings.logger.color_palette.BET_GENERAL, - }, - ) - def skip(self) -> bool: - if self.settings.filter_condition is not None: - # key == by , condition == where - key = self.settings.filter_condition.by - condition = self.settings.filter_condition.where - value = self.settings.filter_condition.value - - fixed_key = ( - key - if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS] - else key.replace("decision", "total") - ) - if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]: - compared_value = ( - self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] - ) - else: - outcome_index = char_decision_as_index(self.decision["choice"]) - compared_value = self.outcomes[outcome_index][fixed_key] - - if self.settings.strategy == Strategy.SMART_HIGH_ODDS: - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 - ): - self.__log_skip("No bet on B") - return False, 0 - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 - ): - if not self.settings.only_doubt: - self.__log_skip("No bet on A") - return False, 0 - - if self.__both_odds_too_low() or self.__is_only_doubt(): - if self.__both_odds_too_low(): - self.__log_skip("Odd is too low") - elif self.__is_only_doubt(): - self.__log_skip("Odd is too low and only_doubt activated") - self.__log_skip(f"{self.get_outcome(0)}; {self.get_outcome(1)}") - self.__log_skip(f"Target odd: {self.settings.target_odd}") - if self.settings.always_bet: - self.__log_skip("Odd is too low, but always_bet activated") - if self.settings.always_bet: - return False, 0 - else: - return True, 0 # Skip - - # Check if condition is satisfied - if condition == Condition.GT: - if compared_value > value: - return False, compared_value - elif condition == Condition.LT: - if compared_value < value: - return False, compared_value - elif condition == Condition.GTE: - if compared_value >= value: - return False, compared_value - elif condition == Condition.LTE: - if compared_value <= value: - return False, compared_value - return True, compared_value # Else skip the bet - else: - return False, 0 # Default don't skip the bet - - def calculate_sho_bet(self, index): - low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] - high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - if self.__both_odds_too_low() or self.__is_only_doubt(): - return 10 - elif high_odd_points <= 50: # in case no one bet - return 50 - else: - target_odd = self.settings.target_odd - if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): - # don't bet too much if odds is too high - target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 - return int((low_odd_points / (target_odd - 1)) - high_odd_points) + return Strategy(self.outcomes, self.settings).get_instance().skip() def calculate(self, balance: int) -> dict: - self.decision = {"choice": None, "amount": 0, "id": None} - if self.settings.only_doubt: - self.decision["choice"] = "B" - elif self.settings.strategy == Strategy.MOST_VOTED: - self.decision["choice"] = self.__return_choice(OutcomeKeys.TOTAL_USERS) - elif self.settings.strategy == Strategy.HIGH_ODDS: - self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) - elif self.settings.strategy == Strategy.SMART_HIGH_ODDS: - self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS) - elif self.settings.strategy == Strategy.PERCENTAGE: - self.decision["choice"] = self.__return_choice(OutcomeKeys.ODDS_PERCENTAGE) - elif self.settings.strategy == Strategy.SMART: - difference = abs( - self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] - - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] - ) - self.decision["choice"] = ( - self.__return_choice(OutcomeKeys.ODDS) - if difference < self.settings.percentage_gap - else self.__return_choice(OutcomeKeys.TOTAL_USERS) - ) - - if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) - self.decision["id"] = self.outcomes[index]["id"] - if self.settings.strategy == Strategy.SMART_HIGH_ODDS: - self.decision["amount"] = min( - self.calculate_sho_bet(index), - int(balance * (self.settings.percentage / 100)), - self.settings.max_points, - ) - else: - self.decision["amount"] = min( - int(balance * (self.settings.percentage / 100)), - self.settings.max_points, - ) - if ( - self.settings.stealth_mode is True - and self.decision["amount"] - >= self.outcomes[index][OutcomeKeys.TOP_POINTS] - ): - reduce_amount = uniform(1, 5) - self.decision["amount"] = ( - self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount - ) - self.decision["amount"] = int(self.decision["amount"]) - return self.decision + return Strategy(self.outcomes, self.settings).get_instance().calculate(balance) diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..91f0d1da --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -q diff --git a/tests/test_bet.py b/tests/test_bet.py new file mode 100644 index 00000000..6c3f69c2 --- /dev/null +++ b/tests/test_bet.py @@ -0,0 +1,206 @@ +from TwitchChannelPointsMiner.classes.entities.Bet import ( + Bet, + Strategy, + BetSettings, + Condition, + OutcomeKeys, + FilterCondition, + DelayMode +) +import pytest + + +@pytest.fixture +def bet_settings(): + settings = BetSettings( + strategy=Strategy.SMART_HIGH_ODDS, + percentage=50, + target_odd=2.1, + only_doubt=True, + max_points=50000, + stealth_mode=False, + delay_mode=DelayMode.FROM_END, + delay=6 + ) + return settings + + +@pytest.fixture +def outcomes(): + outcomes = [ + { + "percentage_users": 50, + "odds_percentage": 60, + "odds": 1.67, + "top_points": 600, + "total_users": 1, + "total_points": 600, + "decision_users": 1, + "decision_points": 1, + "id": 1 + }, + { + "percentage_users": 50, + "odds_percentage": 40, + "odds": 2.5, + "top_points": 400, + "total_users": 1, + "total_points": 400, + "decision_users": 1, + "decision_points": 1, + "id": 2 + } + ] + return outcomes + + +def test_settings(bet_settings, outcomes): + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 145 + + +def test_settings2(bet_settings, outcomes): + outcomes[1]["odds"] = 12 + outcomes[0]["odds"] = 1.09 + outcomes[0]["top_points"] = 4400 + outcomes[0]["total_points"] = 4400 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["amount"] == 480 + + +def test_settings3(bet_settings, outcomes): + outcomes[1]["odds"] = 13 + outcomes[0]["odds"] = 1.08 + outcomes[1]["top_points"] = 50 + outcomes[1]["total_points"] = 50 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["amount"] == 50 + + +def test_settings4(bet_settings, outcomes): + outcomes[1]["odds"] = 2 + outcomes[0]["odds"] = 2 + outcomes[1]["top_points"] = 600 + outcomes[1]["total_points"] = 600 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["amount"] == 10 + + +def test_settings5(bet_settings, outcomes): + outcomes = [outcomes[1], outcomes[0]] + bet_settings.only_doubt = False + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + assert bet["amount"] == 145 + + +def test_most_voted(bet_settings, outcomes): + bet_settings.only_doubt = False + bet_settings.strategy = Strategy.MOST_VOTED + bet_settings.percentage = 20 + outcomes[0]["total_users"] = 1 + outcomes[1]["total_users"] = 2 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 200 + outcomes[0]["total_users"] = 2 + outcomes[1]["total_users"] = 1 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + + +def test_high_odds(bet_settings, outcomes): + bet_settings.only_doubt = False + bet_settings.strategy = Strategy.HIGH_ODDS + bet_settings.percentage = 20 + outcomes[0]["odds"] = 2 + outcomes[1]["odds"] = 3 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 200 + outcomes[0]["odds"] = 3 + outcomes[1]["odds"] = 2 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + + +def test_percentage(bet_settings, outcomes): + bet_settings.only_doubt = False + bet_settings.strategy = Strategy.PERCENTAGE + bet_settings.percentage = 20 + outcomes[0]["odds_percentage"] = 2 + outcomes[1]["odds_percentage"] = 3 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 200 + outcomes[0]["odds_percentage"] = 3 + outcomes[1]["odds_percentage"] = 2 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + + +def test_smart(bet_settings, outcomes): + bet_settings.only_doubt = False + bet_settings.strategy = Strategy.SMART + bet_settings.percentage_gap = 1 + outcomes[0]["percentage_users"] = 30 + outcomes[1]["percentage_users"] = 70 + outcomes[0]["total_users"] = 30 + outcomes[1]["total_users"] = 70 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 500 + outcomes[0]["percentage_users"] = 60 + outcomes[1]["percentage_users"] = 40 + outcomes[0]["total_users"] = 60 + outcomes[1]["total_users"] = 40 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + + +def test_smart2(bet_settings, outcomes): + bet_settings.only_doubt = False + bet_settings.strategy = Strategy.SMART + bet_settings.percentage_gap = 99 + outcomes[0]["percentage_users"] = 30 + outcomes[1]["percentage_users"] = 70 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 500 + outcomes[0]["percentage_users"] = 60 + outcomes[1]["percentage_users"] = 40 + outcomes[0]["odds"] = 2 + outcomes[1]["odds"] = 1 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + + +def test_only_doubt(bet_settings, outcomes): + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 145 + outcomes[1]["odds"] = 2 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "B" + assert bet["amount"] == 10 + + +def test_skip(bet_settings, outcomes): + bet_settings.filter_condition = FilterCondition( + by=OutcomeKeys.ODDS, + where=Condition.GT, + value=2.4 + ) + skip = Bet(outcomes, bet_settings).skip() + assert skip == (False, 2.5) + + +def test_skip2(bet_settings, outcomes): + bet_settings.filter_condition = FilterCondition( + by=OutcomeKeys.ODDS, + where=Condition.GT, + value=2.6 + ) + skip = Bet(outcomes, bet_settings).skip() + assert skip == (True, 2.5) From 9e196170bb2ada7d979d087118c74662aad215a4 Mon Sep 17 00:00:00 2001 From: 1v Date: Sat, 9 Oct 2021 23:12:50 +0000 Subject: [PATCH 005/140] Add StrategySettings * Move Strategy class from Bet.py into separate Strategy.py --- .../classes/entities/Bet.py | 288 ++---------------- .../classes/entities/Strategy.py | 285 +++++++++++++++++ .../classes/entities/__init__.py | 1 - example.py | 7 +- tests/test_bet.py | 80 +++-- 5 files changed, 379 insertions(+), 282 deletions(-) create mode 100644 TwitchChannelPointsMiner/classes/entities/Strategy.py diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 15001e7b..42cd6a44 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -1,241 +1,14 @@ import copy -import logging from enum import Enum, auto -from random import uniform from millify import millify -from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round - -logger = logging.getLogger(__name__) - - -class Strategy(object): - MOST_VOTED = "MostVoted" - HIGH_ODDS = "HighOdds" - SMART_HIGH_ODDS = "SmartHighOdds" - PERCENTAGE = "Percentage" - SMART = "Smart" - - def __init__(self, outcomes: list, settings: object): - self.outcomes = outcomes - self.decision: dict = {} - self.settings = settings - - def get_instance(self): - subclass = globals()[self.settings.strategy] - return subclass(self.outcomes, self.settings) - - def return_choice(self, key) -> str: - return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B" - - def calculate_before(self): - self.decision = {"choice": None, "amount": 0, "id": None} - - def calculate_middle(self): - pass - - def calculate_after(self, balance: int): - if self.settings.only_doubt: - self.decision["choice"] = "B" - if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) - self.decision["id"] = self.outcomes[index]["id"] - amounts = [ - self.decision["amount"], - int(balance * (self.settings.percentage / 100)), - self.settings.max_points, - ] - self.decision["amount"] = min(x for x in amounts if x != 0) - if ( - self.settings.stealth_mode is True - and self.decision["amount"] - >= self.outcomes[index][OutcomeKeys.TOP_POINTS] - ): - reduce_amount = uniform(1, 5) - self.decision["amount"] = ( - self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount - ) - self.decision["amount"] = int(self.decision["amount"]) - - def calculate(self, balance: int) -> dict: - self.calculate_before() - self.calculate_middle() - self.calculate_after(balance) - return self.decision - - def skip_before(self): - if self.settings.always_bet: - self.log_skip("Odd is too low, but always_bet activated") - return False, 0 - - def skip_middle(self): - pass - - def skip_after(self): - if self.settings.filter_condition is not None: - self.decision.setdefault("choice", None) # for tests - # key == by , condition == where - key = self.settings.filter_condition.by - condition = self.settings.filter_condition.where - value = self.settings.filter_condition.value - - fixed_key = ( - key - if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS] - else key.replace("decision", "total") - ) - if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]: - compared_value = ( - self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] - ) - else: - outcome_index = char_decision_as_index(self.decision["choice"]) - compared_value = self.outcomes[outcome_index][fixed_key] - - # Check if condition is satisfied - if condition == Condition.GT: - if compared_value > value: - return False, compared_value - elif condition == Condition.LT: - if compared_value < value: - return False, compared_value - elif condition == Condition.GTE: - if compared_value >= value: - return False, compared_value - elif condition == Condition.LTE: - if compared_value <= value: - return False, compared_value - return True, compared_value # Else skip the bet - else: - return False, 0 # Default don't skip the bet - - def skip(self) -> bool: - skip_results = [self.skip_before(), self.skip_middle(), self.skip_after()] - return next(item for item in skip_results if item is not None) - - def __str__(self): - return self.name - - -class SmartHighOdds(Strategy): - def both_odds_too_low(self) -> bool: - return ( - self.outcomes[0][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - ) - - def is_only_doubt(self) -> bool: - return ( - self.outcomes[1][OutcomeKeys.ODDS] <= self.settings.target_odd - and self.settings.only_doubt - ) - - def log_skip(self, string) -> str: - logger.info( - string, - extra={ - "emoji": ":pushpin:", - "color": Settings.logger.color_palette.BET_GENERAL, - }, - ) - - def skip_middle(self): - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 - ): - self.log_skip("No bet on B") - return False, 0 - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 - ): - if not self.settings.only_doubt: - self.log_skip("No bet on A") - return False, 0 - - if self.both_odds_too_low() or self.is_only_doubt(): - if self.both_odds_too_low(): - self.log_skip("Odd is too low") - elif self.is_only_doubt(): - self.log_skip("Odd is too low and only_doubt activated") - self.log_skip(f"{self.get_outcome(0)}; {self.get_outcome(1)}") - self.log_skip(f"Target odd: {self.settings.target_odd}") - return True, 0 # Skip - - def calculate_sho_bet(self, index): - low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] - high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - if self.both_odds_too_low() or self.is_only_doubt(): - return 10 - elif high_odd_points <= 50: # in case no one bet - return 50 - else: - target_odd = self.settings.target_odd - if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): - # don't bet too much if odds is too high - target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 - return int((low_odd_points / (target_odd - 1)) - high_odd_points) - - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) - self.decision["amount"] = int(self.calculate_sho_bet(index)) - - -class MostVoted(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) - - -class HighOdds(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - - -class Percentage(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) - - -class Smart(Strategy): - def calculate_middle(self): - difference = abs( - self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] - - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] - ) - self.decision["choice"] = ( - self.return_choice(OutcomeKeys.ODDS) - if difference < self.settings.percentage_gap - else self.return_choice(OutcomeKeys.TOTAL_USERS) - ) - - -class Condition(Enum): - GT = auto() - LT = auto() - GTE = auto() - LTE = auto() - - def __str__(self): - return self.name - - -class OutcomeKeys(object): - # Real key on Bet dict [''] - PERCENTAGE_USERS = "percentage_users" - ODDS_PERCENTAGE = "odds_percentage" - ODDS = "odds" - TOP_POINTS = "top_points" - # Real key on Bet dict [''] - Sum() - TOTAL_USERS = "total_users" - TOTAL_POINTS = "total_points" - # This key does not exist - DECISION_USERS = "decision_users" - DECISION_POINTS = "decision_points" +from TwitchChannelPointsMiner.classes.entities.Strategy import ( + OutcomeKeys, + Strategy, + StrategySettings, +) +from TwitchChannelPointsMiner.utils import float_round class DelayMode(Enum): @@ -267,61 +40,63 @@ class BetSettings(object): __slots__ = [ "strategy", "percentage", - "percentage_gap", "max_points", - "target_odd", "only_doubt", - "always_bet", "minimum_points", "stealth_mode", "filter_condition", "delay", "delay_mode", + "strategy_settings", ] def __init__( self, strategy: Strategy = None, percentage: int = None, - percentage_gap: int = None, max_points: int = None, - target_odd: float = None, only_doubt: bool = None, - always_bet: bool = None, minimum_points: int = None, stealth_mode: bool = None, filter_condition: FilterCondition = None, delay: float = None, delay_mode: DelayMode = None, + strategy_settings: StrategySettings = None, ): self.strategy = strategy self.percentage = percentage - self.percentage_gap = percentage_gap self.max_points = max_points - self.target_odd = target_odd self.only_doubt = only_doubt - self.always_bet = always_bet self.minimum_points = minimum_points self.stealth_mode = stealth_mode self.filter_condition = filter_condition self.delay = delay self.delay_mode = delay_mode + self.strategy_settings = StrategySettings( + strategy=strategy, **strategy_settings + ).get_instance() 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 20 - self.max_points = self.max_points if not None else 50000 - self.target_odd = self.target_odd if not None else 3 - self.only_doubt = self.only_doubt if not None else False - self.always_bet = self.always_bet if not None else True - self.minimum_points = self.minimum_points if not None else 0 - self.stealth_mode = self.stealth_mode if not None else False - self.delay = self.delay if not None else 6 - self.delay_mode = self.delay_mode if not None else DelayMode.FROM_END + self.strategy = self.strategy if self.strategy is not None else Strategy.SMART + self.percentage = self.percentage if self.percentage is not None else 5 + self.max_points = self.max_points if self.max_points is not None else 50000 + self.only_doubt = self.only_doubt if self.only_doubt is not None else False + self.minimum_points = ( + self.minimum_points if self.minimum_points is not None else 0 + ) + self.stealth_mode = ( + self.stealth_mode if self.stealth_mode is not None else False + ) + self.delay = self.delay if self.delay is not None else 6 + self.delay_mode = ( + self.delay_mode if self.delay_mode is not None else DelayMode.FROM_END + ) + self.strategy_settings = ( + self.strategy_settings if self.strategy_settings is not None else {} + ) def __repr__(self): - return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, minimum_points={self.minimum_points}, stealth_mode={self.stealth_mode})" + return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, max_points={self.max_points}, minimum_points={self.minimum_points}, stealth_mode={self.stealth_mode})" class Bet(object): @@ -425,4 +200,7 @@ def skip(self) -> bool: return Strategy(self.outcomes, self.settings).get_instance().skip() def calculate(self, balance: int) -> dict: - return Strategy(self.outcomes, self.settings).get_instance().calculate(balance) + self.decision = ( + Strategy(self.outcomes, self.settings).get_instance().calculate(balance) + ) + return self.decision diff --git a/TwitchChannelPointsMiner/classes/entities/Strategy.py b/TwitchChannelPointsMiner/classes/entities/Strategy.py new file mode 100644 index 00000000..d5230cdd --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/Strategy.py @@ -0,0 +1,285 @@ +import logging +from enum import Enum, auto +from random import uniform + +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import char_decision_as_index + +logger = logging.getLogger(__name__) + + +class Condition(Enum): + GT = auto() + LT = auto() + GTE = auto() + LTE = auto() + + def __str__(self): + return self.name + + +class OutcomeKeys(object): + # Real key on Bet dict [''] + PERCENTAGE_USERS = "percentage_users" + ODDS_PERCENTAGE = "odds_percentage" + ODDS = "odds" + TOP_POINTS = "top_points" + # Real key on Bet dict [''] - Sum() + TOTAL_USERS = "total_users" + TOTAL_POINTS = "total_points" + # This key does not exist + DECISION_USERS = "decision_users" + DECISION_POINTS = "decision_points" + + +class Strategy(object): + MOST_VOTED = "MostVoted" + HIGH_ODDS = "HighOdds" + SMART_HIGH_ODDS = "SmartHighOdds" + PERCENTAGE = "Percentage" + SMART = "Smart" + + def __init__(self, outcomes: list, settings: object): + self.outcomes = outcomes + self.decision: dict = {} + self.settings = settings + + def get_instance(self): + subclass = globals()[self.settings.strategy] + return subclass(self.outcomes, self.settings) + + def return_choice(self, key) -> str: + return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B" + + def calculate_before(self): + self.decision = {"choice": None, "amount": 0, "id": None} + + def calculate_middle(self): + pass + + def calculate_after(self, balance: int): + if self.settings.only_doubt: + self.decision["choice"] = "B" + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["id"] = self.outcomes[index]["id"] + amounts = [ + self.decision["amount"], + int(balance * (self.settings.percentage / 100)), + self.settings.max_points, + ] + self.decision["amount"] = min(x for x in amounts if x != 0) + if ( + self.settings.stealth_mode is True + and self.decision["amount"] + >= self.outcomes[index][OutcomeKeys.TOP_POINTS] + ): + reduce_amount = uniform(1, 5) + self.decision["amount"] = ( + self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount + ) + self.decision["amount"] = int(self.decision["amount"]) + + def calculate(self, balance: int) -> dict: + self.calculate_before() + self.calculate_middle() + self.calculate_after(balance) + return self.decision + + def skip_before(self): + pass + + def skip_middle(self): + pass + + def skip_after(self): + if self.settings.filter_condition is not None: + self.decision.setdefault("choice", None) # for tests + # key == by , condition == where + key = self.settings.filter_condition.by + condition = self.settings.filter_condition.where + value = self.settings.filter_condition.value + + fixed_key = ( + key + if key not in [OutcomeKeys.DECISION_USERS, OutcomeKeys.DECISION_POINTS] + else key.replace("decision", "total") + ) + if key in [OutcomeKeys.TOTAL_USERS, OutcomeKeys.TOTAL_POINTS]: + compared_value = ( + self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] + ) + else: + outcome_index = char_decision_as_index(self.decision["choice"]) + compared_value = self.outcomes[outcome_index][fixed_key] + + # Check if condition is satisfied + if condition == Condition.GT: + if compared_value > value: + return False, compared_value + elif condition == Condition.LT: + if compared_value < value: + return False, compared_value + elif condition == Condition.GTE: + if compared_value >= value: + return False, compared_value + elif condition == Condition.LTE: + if compared_value <= value: + return False, compared_value + return True, compared_value # Else skip the bet + else: + return False, 0 # Default don't skip the bet + + def skip(self) -> bool: + skip_results = [self.skip_before(), self.skip_middle(), self.skip_after()] + return next(item for item in skip_results if item is not None) + + def __str__(self): + return self.name + + +class StrategySettings(object): + def __init__(self, strategy: Strategy = None, **kwargs): + subclass = globals()[f"{strategy}Settings"] + self.instance = subclass(**kwargs) + self.instance.default() + + def get_instance(self): + return self.instance + + +class SmartHighOddsSettings(object): + __slots__ = [ + "target_odd", + "always_bet", + ] + + def __init__( + self, + target_odd: float = None, + always_bet: bool = None, + ): + self.target_odd = target_odd + self.always_bet = always_bet + + def default(self): + self.target_odd = self.target_odd if self.target_odd is not None else 3 + self.always_bet = self.always_bet if self.always_bet is not None else False + + +class SmartSettings(object): + __slots__ = [ + "percentage_gap", + ] + + def __init__( + self, + percentage_gap: float = None, + ): + self.percentage_gap = percentage_gap + + def default(self): + self.percentage_gap = ( + self.percentage_gap if self.percentage_gap is not None else 20 + ) + + +class SmartHighOdds(Strategy): + def both_odds_too_low(self) -> bool: + return ( + self.outcomes[0][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + ) + + def is_only_doubt(self) -> bool: + return ( + self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.settings.only_doubt + ) + + def log_skip(self, string) -> str: + logger.info( + string, + extra={ + "emoji": ":pushpin:", + "color": Settings.logger.color_palette.BET_GENERAL, + }, + ) + + def skip_middle(self): + if self.settings.strategy_settings.always_bet: + self.log_skip("always_bet activated") + return False, 0 + + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 + ): + self.log_skip("No bet on B") + return False, 0 + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + ): + if not self.settings.only_doubt: + self.log_skip("No bet on A") + return False, 0 + + if self.both_odds_too_low() or self.is_only_doubt(): + if self.both_odds_too_low(): + self.log_skip("Odd is too low") + elif self.is_only_doubt(): + self.log_skip("Odd is too low and only_doubt activated") + self.log_skip(f"Target odd: {self.settings.strategy_settings.target_odd}") + return True, 0 # Skip + + def calculate_sho_bet(self, index): + low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] + high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + if self.both_odds_too_low() or self.is_only_doubt(): + return 10 + elif high_odd_points <= 50: # in case no one bet + return 50 + else: + target_odd = self.settings.strategy_settings.target_odd + if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): + # don't bet too much if odds is too high + target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 + return int((low_odd_points / (target_odd - 1)) - high_odd_points) + + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["amount"] = int(self.calculate_sho_bet(index)) + + +class MostVoted(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) + + +class HighOdds(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + + +class Percentage(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) + + +class Smart(Strategy): + def calculate_middle(self): + difference = abs( + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] + ) + self.decision["choice"] = ( + self.return_choice(OutcomeKeys.ODDS) + if difference < self.settings.strategy_settings.percentage_gap + else self.return_choice(OutcomeKeys.TOTAL_USERS) + ) diff --git a/TwitchChannelPointsMiner/classes/entities/__init__.py b/TwitchChannelPointsMiner/classes/entities/__init__.py index 8b137891..e69de29b 100644 --- a/TwitchChannelPointsMiner/classes/entities/__init__.py +++ b/TwitchChannelPointsMiner/classes/entities/__init__.py @@ -1 +0,0 @@ - diff --git a/example.py b/example.py index 4ea82280..bd00d7c5 100644 --- a/example.py +++ b/example.py @@ -39,8 +39,6 @@ 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) - target_odd=3, # Target odd for SMART_HIGH_ODDS strategy only_doubt=False, # Will only doubt (bet on B). If set to True will overwrite strategy bet decision max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33 @@ -51,7 +49,10 @@ by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value value=800 - ) + ), + strategy_settings={ + "percentage_gap": 20 # Gap difference between outcomesA and outcomesB (for SMART stragegy) + } ) ) ) diff --git a/tests/test_bet.py b/tests/test_bet.py index 6c3f69c2..b9c5ba70 100644 --- a/tests/test_bet.py +++ b/tests/test_bet.py @@ -1,26 +1,32 @@ from TwitchChannelPointsMiner.classes.entities.Bet import ( Bet, - Strategy, BetSettings, - Condition, - OutcomeKeys, FilterCondition, DelayMode ) +from TwitchChannelPointsMiner.classes.entities.Strategy import ( + Strategy, + StrategySettings, + Condition, + OutcomeKeys +) import pytest - +from TwitchChannelPointsMiner.logger import LoggerSettings +from TwitchChannelPointsMiner.classes.Settings import Settings @pytest.fixture def bet_settings(): settings = BetSettings( strategy=Strategy.SMART_HIGH_ODDS, percentage=50, - target_odd=2.1, - only_doubt=True, + only_doubt=False, max_points=50000, stealth_mode=False, delay_mode=DelayMode.FROM_END, - delay=6 + delay=6, + strategy_settings={ + "target_odd": 2.1 + } ) return settings @@ -55,9 +61,11 @@ def outcomes(): def test_settings(bet_settings, outcomes): - bet = Bet(outcomes, bet_settings).calculate(1000) - assert bet["choice"] == "B" - assert bet["amount"] == 145 + bet = Bet(outcomes, bet_settings) + calc = bet.calculate(1000) + assert calc["choice"] == "B" + assert calc["amount"] == 145 + assert bet.decision == {"amount": 145, "choice": "B", "id": 2} # important def test_settings2(bet_settings, outcomes): @@ -89,14 +97,23 @@ def test_settings4(bet_settings, outcomes): def test_settings5(bet_settings, outcomes): outcomes = [outcomes[1], outcomes[0]] - bet_settings.only_doubt = False bet = Bet(outcomes, bet_settings).calculate(1000) assert bet["choice"] == "A" assert bet["amount"] == 145 +def test_always_bet(bet_settings, outcomes): + Settings.logger = LoggerSettings() + outcomes[1]["odds"] = 2 + outcomes[0]["odds"] = 2 + skip = Bet(outcomes, bet_settings).skip() + assert skip == (True, 0) + bet_settings.strategy_settings.always_bet = True + skip = Bet(outcomes, bet_settings).skip() + assert skip == (False, 0) + + def test_most_voted(bet_settings, outcomes): - bet_settings.only_doubt = False bet_settings.strategy = Strategy.MOST_VOTED bet_settings.percentage = 20 outcomes[0]["total_users"] = 1 @@ -111,7 +128,6 @@ def test_most_voted(bet_settings, outcomes): def test_high_odds(bet_settings, outcomes): - bet_settings.only_doubt = False bet_settings.strategy = Strategy.HIGH_ODDS bet_settings.percentage = 20 outcomes[0]["odds"] = 2 @@ -126,7 +142,6 @@ def test_high_odds(bet_settings, outcomes): def test_percentage(bet_settings, outcomes): - bet_settings.only_doubt = False bet_settings.strategy = Strategy.PERCENTAGE bet_settings.percentage = 20 outcomes[0]["odds_percentage"] = 2 @@ -141,16 +156,20 @@ def test_percentage(bet_settings, outcomes): def test_smart(bet_settings, outcomes): - bet_settings.only_doubt = False - bet_settings.strategy = Strategy.SMART - bet_settings.percentage_gap = 1 + bet_settings = BetSettings( + strategy=Strategy.SMART, + strategy_settings={ + "percentage_gap": 1 + } + ) + bet_settings.default() outcomes[0]["percentage_users"] = 30 outcomes[1]["percentage_users"] = 70 outcomes[0]["total_users"] = 30 outcomes[1]["total_users"] = 70 bet = Bet(outcomes, bet_settings).calculate(1000) assert bet["choice"] == "B" - assert bet["amount"] == 500 + assert bet["amount"] == 50 outcomes[0]["percentage_users"] = 60 outcomes[1]["percentage_users"] = 40 outcomes[0]["total_users"] = 60 @@ -160,14 +179,18 @@ def test_smart(bet_settings, outcomes): def test_smart2(bet_settings, outcomes): - bet_settings.only_doubt = False - bet_settings.strategy = Strategy.SMART - bet_settings.percentage_gap = 99 + bet_settings = BetSettings( + strategy=Strategy.SMART, + strategy_settings={ + "percentage_gap": 99 + } + ) + bet_settings.default() outcomes[0]["percentage_users"] = 30 outcomes[1]["percentage_users"] = 70 bet = Bet(outcomes, bet_settings).calculate(1000) assert bet["choice"] == "B" - assert bet["amount"] == 500 + assert bet["amount"] == 50 outcomes[0]["percentage_users"] = 60 outcomes[1]["percentage_users"] = 40 outcomes[0]["odds"] = 2 @@ -180,7 +203,11 @@ def test_only_doubt(bet_settings, outcomes): bet = Bet(outcomes, bet_settings).calculate(1000) assert bet["choice"] == "B" assert bet["amount"] == 145 - outcomes[1]["odds"] = 2 + outcomes[1]["odds"] = 1.5 + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["choice"] == "A" + assert bet["amount"] == 10 + bet_settings.only_doubt = True bet = Bet(outcomes, bet_settings).calculate(1000) assert bet["choice"] == "B" assert bet["amount"] == 10 @@ -204,3 +231,10 @@ def test_skip2(bet_settings, outcomes): ) skip = Bet(outcomes, bet_settings).skip() assert skip == (True, 2.5) + + +def test_skip3(bet_settings, outcomes): + Settings.logger = LoggerSettings() + bet_settings.strategy_settings.target_odd = 2.5 + skip = Bet(outcomes, bet_settings).skip() + assert skip == (True, 0) From 54de3b6ce50a29ce2fdd7edf0b5cb10a8e4a0545 Mon Sep 17 00:00:00 2001 From: 1v Date: Sun, 10 Oct 2021 22:43:41 +0000 Subject: [PATCH 006/140] Add tests --- tests/test_bet.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_bet.py b/tests/test_bet.py index b9c5ba70..935581f7 100644 --- a/tests/test_bet.py +++ b/tests/test_bet.py @@ -102,6 +102,35 @@ def test_settings5(bet_settings, outcomes): assert bet["amount"] == 145 +def test_update_outcomes(bet_settings, outcomes): + bet = Bet(outcomes, bet_settings) + outcomes[0]["top_points"] = 0 + outcomes[0]["top_predictors"] = [{"points": 100}, {"points": 200}] + outcomes[1]["top_predictors"] = [{"points": 100}, {"points": 300}] + outcomes[0]["total_users"] = 1 + outcomes[1]["total_users"] = 3 + outcomes[0]["total_points"] = 800 + outcomes[1]["total_points"] = 200 + bet.update_outcomes(outcomes) + assert bet.outcomes[0]["top_points"] == 200 + assert bet.outcomes[1]["top_points"] == 300 + assert bet.outcomes[0]["percentage_users"] == 25 + assert bet.outcomes[1]["percentage_users"] == 75 + assert bet.outcomes[0]["odds"] == 1.25 + assert bet.outcomes[1]["odds"] == 5 + assert bet.outcomes[0]["odds_percentage"] == 80 + assert bet.outcomes[1]["odds_percentage"] == 20 + + +def test_stealth_mode(bet_settings, outcomes): + bet_settings.stealth_mode = True + outcomes[1]["top_points"] = 80 + for x in range(10): + bet = Bet(outcomes, bet_settings).calculate(1000) + assert bet["amount"] >= 75 + assert bet["amount"] <= 79 + + def test_always_bet(bet_settings, outcomes): Settings.logger = LoggerSettings() outcomes[1]["odds"] = 2 From 86245b33a98206cca83e89269df34d58c8399ea4 Mon Sep 17 00:00:00 2001 From: 1v Date: Sun, 10 Oct 2021 22:47:44 +0000 Subject: [PATCH 007/140] Add pytest to github CI --- .github/workflows/code-checker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/code-checker.yml b/.github/workflows/code-checker.yml index 2c71c890..414c6c8d 100644 --- a/.github/workflows/code-checker.yml +++ b/.github/workflows/code-checker.yml @@ -27,6 +27,7 @@ jobs: pip3 install pyflakes pip3 install black pip3 install isort + pip3 install pytest - name: Detect errors with pyflakes run: pyflakes TwitchChannelPointsMiner @@ -36,3 +37,6 @@ jobs: - name: Lint with isort run: isort TwitchChannelPointsMiner --profile black --check --diff + + - name: Run tests + run: pytest From c0c8dc6af256036d0b41c23ca883d27e30f4b9e9 Mon Sep 17 00:00:00 2001 From: 1v Date: Sun, 10 Oct 2021 22:54:47 +0000 Subject: [PATCH 008/140] Add requirements to CI --- .github/workflows/code-checker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code-checker.yml b/.github/workflows/code-checker.yml index 414c6c8d..f8529d90 100644 --- a/.github/workflows/code-checker.yml +++ b/.github/workflows/code-checker.yml @@ -24,6 +24,7 @@ jobs: - name: Install Requirements run: | pip3 install --upgrade pip + pip3 install -r requirements.txt pip3 install pyflakes pip3 install black pip3 install isort From 07e125e5cb697a5145f381eec76b8a19d653c812 Mon Sep 17 00:00:00 2001 From: 1v Date: Mon, 11 Oct 2021 01:45:43 +0000 Subject: [PATCH 009/140] Fix README and example.py --- README.md | 114 +++++++++++++++++++++++++++++------------------------ example.py | 3 +- 2 files changed, 64 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 622302aa..792f35c3 100644 --- a/README.md +++ b/README.md @@ -134,23 +134,23 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - 📊 BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") - Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) - Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) - Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") - Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) - Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) - Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) - Result: {'type': 'WIN', 'won': 6531} + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") - Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) - Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) - Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 %d/%m/%y %H:%M:%S - 💰 CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) @@ -180,7 +180,8 @@ from colorama import Fore from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette from TwitchChannelPointsMiner.classes.Settings import Priority -from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Strategy import Strategy, Condition, OutcomeKeys from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings twitch_miner = TwitchChannelPointsMiner( @@ -214,8 +215,6 @@ twitch_miner = TwitchChannelPointsMiner( 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) - target_odd=3, # Target odd for SMART_HIGH_ODDS strategy max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33 delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer @@ -225,7 +224,10 @@ twitch_miner = TwitchChannelPointsMiner( by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS] where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value value=800 - ) + ), + strategy_settings={ + "percentage_gap": 20 # Gap difference between outcomesA and outcomesB (for SMART stragegy) + } ) ) ) @@ -290,15 +292,15 @@ Available values are the following: You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. ### LoggerSettings -| Key | Type | Default | Description | -|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `save` | bool | True | If you want to save logs in file (suggested) | -| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | -| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | -| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | -| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | -| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | +| 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 printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | #### Color Palette Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. @@ -337,25 +339,32 @@ ColorPalette( ``` ### StreamerSettings -| Key | Type | Default | Description | -|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | -| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | -| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | -| `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 | +| Key | Type | Default | Description | +|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `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 | -|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `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) | -| `target_odd` | float | 3 | Target odd for SMART_HIGH_ODDS strategy | -| `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) | -| `join_chat` | bool | True | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | -| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | -| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | +| 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 | +| `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) | +| `join_chat` | bool | True | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | +| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | +| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | +| `strategy_settings` | dict | {} | Settings specific to strategy | +| `only_doubt` | bool | False | Always bet on B (will overwrite strategy bet decision) | + +### StrategySettings +| Strategy | Key | Type | Default | Description | +|-------------------- |--------------------- |----------------- |----------- |------------ | +| SMART | `percentage_gap` | int | 20 | Gap difference between outcomesA and outcomesB | +| SMART_HIGH_ODDS | `target_odd` | float | 3 | Bet that much points so the odd will be not less then `target_odd` (default 3) | +| SMART_HIGH_ODDS | `always_bet` | bool | False | Always bet minimum 10 points for stats collecting | #### Bet strategy @@ -372,16 +381,17 @@ 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' - **SMART_HIGH_ODDS**: No bet because highest odd 2.27 is lower than default target odd (3) +- - If `target_odd` set to 2.1 then bet will be (6888 / (2.1 - 1)) - 5437 = 824. Making final odds 2.1/1.9 - **PERCENTAGE**: The highest percentage is 56% for **'under 7.5'** - **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. +- - In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' -In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' ### FilterCondition -| Key | Type | Default | Description | -|------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `by` | OutcomeKeys | None | Key to apply the filter | -| `where` | Condition | None | Condition that should match for place bet | -| `value` | number | None | Value to compare | +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | Allowed values for `by` are: - `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] diff --git a/example.py b/example.py index bd00d7c5..63f773f8 100644 --- a/example.py +++ b/example.py @@ -5,7 +5,8 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette from TwitchChannelPointsMiner.classes.Settings import Priority -from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings, FilterCondition, DelayMode +from TwitchChannelPointsMiner.classes.entities.Strategy import Strategy, Condition, OutcomeKeys from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings twitch_miner = TwitchChannelPointsMiner( From f0ab436d6749c5ca038665856b5cbb13d40a93ca Mon Sep 17 00:00:00 2001 From: 1v Date: Wed, 13 Oct 2021 01:37:21 +0000 Subject: [PATCH 010/140] Add stats --- .gitignore | 7 +- stats/README.md | 12 +++ stats/requirements.txt | 4 + stats/settings.py.example | 17 ++++ stats/stats.py | 194 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 stats/README.md create mode 100644 stats/requirements.txt create mode 100644 stats/settings.py.example create mode 100644 stats/stats.py diff --git a/.gitignore b/.gitignore index c1445f5e..3b4a9abb 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,9 @@ cookies/* logs/* screenshots/* htmls/* -analytics/* \ No newline at end of file +analytics/* + +# Stats +stats/settings.py +stats/*.html +stats/*.json diff --git a/stats/README.md b/stats/README.md new file mode 100644 index 00000000..23731507 --- /dev/null +++ b/stats/README.md @@ -0,0 +1,12 @@ +# How it works +![image](https://user-images.githubusercontent.com/6566370/137048948-55c8f003-312b-46b6-8137-1e82dbe52ef7.png) +# Install requirements: +``` +pip install -r requirements.txt +``` +Rename `settings.py.example` to `settings.py` and change settings. +# Run script +``` +python stats.py +``` +To exit script press `q`. diff --git a/stats/requirements.txt b/stats/requirements.txt new file mode 100644 index 00000000..6c829d21 --- /dev/null +++ b/stats/requirements.txt @@ -0,0 +1,4 @@ +humanize==3.5.0 +prettytable==2.1.0 +culour==0.1 +humanfriendly==9.1 diff --git a/stats/settings.py.example b/stats/settings.py.example new file mode 100644 index 00000000..2fb7efcd --- /dev/null +++ b/stats/settings.py.example @@ -0,0 +1,17 @@ +settings = { + # your twitch nickname + "username": "xqcow", + # stats saved as html table, make soft link to this file in directory + # with http access and view your stats online like 1.1.1.1/stats.html + "html_filename": "stats.html", + # stats saved as json data + "json_filename": "stats.json", + # columns in table that show points earned, in hours + "columns": [3, 8, 24, 48, 24 * 7, 24 * 7 * 2], + # blacklisted streamers if you don't wan't them to be present in table + "blacklist": ["forsen"], + # how often to update data in table (in seconds) + "refresh_rate": 60, + "debug": False, + "bugged_terminal": True, +} diff --git a/stats/stats.py b/stats/stats.py new file mode 100644 index 00000000..12f75540 --- /dev/null +++ b/stats/stats.py @@ -0,0 +1,194 @@ +import curses +import json +import re +import time +from datetime import datetime, timedelta +from os import listdir +from os.path import isfile, join +from pathlib import Path + +import culour +import humanfriendly +import humanize +from prettytable import PrettyTable, prettytable +from settings import settings + + +def timestamp_format(entry): + entry["x"] = int(entry["x"] / 1000) + return entry + + +def repeat_to_length(s, wanted): + return (s * (wanted // len(s) + 1))[:wanted] + + +def days_filter(x, hours): + yesterday = datetime.today() - timedelta(hours=hours) + return datetime.fromtimestamp(x["x"]) > yesterday + + +def get_points_from_data(data, hours, current_points): + def list_filter(x): + return days_filter(x, hours) + + data_list = list(filter(list_filter, data["series"])) + data_list = data_list[0] if data_list else None + if data_list: + return current_points - data_list["y"] + else: + return 0 + + +class COLORS(object): + MAGENTA = "\033[95m" + BLUE = "\033[94m" + GREEN = "\033[92m" + YELLOW = "\033[93m" + RED = "\033[91m" + END = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +def make_green(text): + return f"{COLORS.GREEN}{text}{COLORS.END}" + + +def make_red(text): + return f"{COLORS.RED}{text}{COLORS.END}" + + +def set_colors(array): + final_array = [] + for k, row in enumerate(array): + if k == len(array) - 1: + final_array.append(row) + continue + final_row = [] + for i, cell in enumerate(row): + + def check_points(points): + if points == 0: + return points + elif points > 0: + return make_green(points) + elif points < 0: + return make_red(points) + + if i == 0 or i == 1: + final_row.append(cell) + elif i < len(settings["columns"]) + 2: + final_row.append(check_points(cell)) + else: + final_row.append(cell) + final_array.append(final_row) + return final_array + + +def get_total_row(final_array): + array = [] + for i in range(len(settings["columns"]) + 3): + if i == 0: + array.append("Total") + elif i >= len(settings["columns"]) + 2: + array.append("") + else: + array.append(humanize.intcomma(sum(int(row[i]) for row in final_array))) + return array + + +def get_array(): + mypath = f"../analytics/{settings['username']}" + onlyfiles = [f for f in listdir(mypath) if isfile(join(mypath, f))] + final_array = [] + for streamer in onlyfiles: + txt = Path(f"{mypath}/{streamer}").read_text() + try: + data = json.loads(txt) + except: # noqa E722 + continue + streamer = re.sub(r"\.json$", "", streamer) + if streamer in settings["blacklist"]: + continue + data["series"] = list(map(timestamp_format, data["series"])) + last_entry = data["series"][-1] + changes = [] + for column in settings["columns"]: + changes.append(get_points_from_data(data, column, last_entry["y"])) + date = datetime.fromtimestamp(last_entry["x"]) + # six_minutes_ago = datetime.today() - timedelta(minutes=5) + # if not no_colors and date > six_minutes_ago: + # streamer = make_green(streamer) + date = humanize.naturaltime(date) + final_array.append([streamer, last_entry["y"]] + changes + [date]) + final_array = sorted(final_array, key=lambda x: x[1], reverse=True) + final_array.append(get_total_row(final_array)) + return final_array + + +def header(): + result = ["Streamer", "Points"] + for column in settings["columns"]: + result.append(humanfriendly.format_timespan(timedelta(hours=column))) + result.append("Updated") + return result + + +def get_table(): + array = get_array() + + if settings["json_filename"]: + f = open(settings["json_filename"], "w") + f.write(json.dumps(array)) + f.close() + + if settings["html_filename"]: + x = PrettyTable(hrules=prettytable.ALL) + x.field_names = header() + for streamer in array: + x.add_row(streamer) + x.align = "l" + x.format = True + f = open(settings["html_filename"], "w") + f.write(x.get_html_string()) + f.close() + + final_array = set_colors(array) + x = PrettyTable(hrules=prettytable.ALL) + x.field_names = header() + for streamer in final_array: + x.add_row(streamer) + x.align = "l" + + return x.get_string().splitlines() + + +def stats(window): + window.nodelay(1) + curses.curs_set(0) + cycle = 0 + table = get_table() + while window.getch() not in [ord(x) for x in ["q", "Q"]]: + cycle += 1 + if cycle == settings["refresh_rate"] * 10: + table = get_table() + cycle = 0 + height, width = window.getmaxyx() + if len(table) < height: + height = len(table) + for column in range(height): + if settings["bugged_terminal"] and table[column][0] == "+": + window.addstr( + column, 0, repeat_to_length("+------", len(table[column])) + ) + else: + if settings["debug"]: + f = open("log.txt", "a") + f.write(table[column] + "\n") + f.close() + culour.addstr(window, column, 0, table[column]) + time.sleep(0.1) + + +curses.wrapper(stats) From d1b32171d28969f102d5204f0b5c6eed2ec2304b Mon Sep 17 00:00:00 2001 From: 1v Date: Fri, 22 Oct 2021 04:42:08 +0000 Subject: [PATCH 011/140] Move each strategy to separate files --- .../classes/entities/Strategy.py | 152 ++---------------- .../classes/entities/strategies/HighOdds.py | 6 + .../classes/entities/strategies/MostVoted.py | 6 + .../classes/entities/strategies/Percentage.py | 6 + .../classes/entities/strategies/Smart.py | 31 ++++ .../entities/strategies/SmartHighOdds.py | 99 ++++++++++++ 6 files changed, 157 insertions(+), 143 deletions(-) create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/Smart.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py diff --git a/TwitchChannelPointsMiner/classes/entities/Strategy.py b/TwitchChannelPointsMiner/classes/entities/Strategy.py index d5230cdd..76fa2228 100644 --- a/TwitchChannelPointsMiner/classes/entities/Strategy.py +++ b/TwitchChannelPointsMiner/classes/entities/Strategy.py @@ -1,12 +1,9 @@ -import logging from enum import Enum, auto +from importlib import import_module from random import uniform -from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.utils import char_decision_as_index -logger = logging.getLogger(__name__) - class Condition(Enum): GT = auto() @@ -45,7 +42,10 @@ def __init__(self, outcomes: list, settings: object): self.settings = settings def get_instance(self): - subclass = globals()[self.settings.strategy] + strategy_module = import_module( + f".{self.settings.strategy}", package=f"{__package__}.strategies" + ) + subclass = getattr(strategy_module, self.settings.strategy) return subclass(self.outcomes, self.settings) def return_choice(self, key) -> str: @@ -140,146 +140,12 @@ def __str__(self): class StrategySettings(object): def __init__(self, strategy: Strategy = None, **kwargs): - subclass = globals()[f"{strategy}Settings"] + strategy_module = import_module( + f".{strategy}", package=f"{__package__}.strategies" + ) + subclass = getattr(strategy_module, f"{strategy}Settings") self.instance = subclass(**kwargs) self.instance.default() def get_instance(self): return self.instance - - -class SmartHighOddsSettings(object): - __slots__ = [ - "target_odd", - "always_bet", - ] - - def __init__( - self, - target_odd: float = None, - always_bet: bool = None, - ): - self.target_odd = target_odd - self.always_bet = always_bet - - def default(self): - self.target_odd = self.target_odd if self.target_odd is not None else 3 - self.always_bet = self.always_bet if self.always_bet is not None else False - - -class SmartSettings(object): - __slots__ = [ - "percentage_gap", - ] - - def __init__( - self, - percentage_gap: float = None, - ): - self.percentage_gap = percentage_gap - - def default(self): - self.percentage_gap = ( - self.percentage_gap if self.percentage_gap is not None else 20 - ) - - -class SmartHighOdds(Strategy): - def both_odds_too_low(self) -> bool: - return ( - self.outcomes[0][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - ) - - def is_only_doubt(self) -> bool: - return ( - self.outcomes[1][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - and self.settings.only_doubt - ) - - def log_skip(self, string) -> str: - logger.info( - string, - extra={ - "emoji": ":pushpin:", - "color": Settings.logger.color_palette.BET_GENERAL, - }, - ) - - def skip_middle(self): - if self.settings.strategy_settings.always_bet: - self.log_skip("always_bet activated") - return False, 0 - - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 - ): - self.log_skip("No bet on B") - return False, 0 - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 - ): - if not self.settings.only_doubt: - self.log_skip("No bet on A") - return False, 0 - - if self.both_odds_too_low() or self.is_only_doubt(): - if self.both_odds_too_low(): - self.log_skip("Odd is too low") - elif self.is_only_doubt(): - self.log_skip("Odd is too low and only_doubt activated") - self.log_skip(f"Target odd: {self.settings.strategy_settings.target_odd}") - return True, 0 # Skip - - def calculate_sho_bet(self, index): - low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] - high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - if self.both_odds_too_low() or self.is_only_doubt(): - return 10 - elif high_odd_points <= 50: # in case no one bet - return 50 - else: - target_odd = self.settings.strategy_settings.target_odd - if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): - # don't bet too much if odds is too high - target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 - return int((low_odd_points / (target_odd - 1)) - high_odd_points) - - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) - self.decision["amount"] = int(self.calculate_sho_bet(index)) - - -class MostVoted(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) - - -class HighOdds(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - - -class Percentage(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) - - -class Smart(Strategy): - def calculate_middle(self): - difference = abs( - self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] - - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] - ) - self.decision["choice"] = ( - self.return_choice(OutcomeKeys.ODDS) - if difference < self.settings.strategy_settings.percentage_gap - else self.return_choice(OutcomeKeys.TOTAL_USERS) - ) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py b/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py new file mode 100644 index 00000000..4fdaa7a5 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class HighOdds(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py b/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py new file mode 100644 index 00000000..f455a958 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class MostVoted(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py b/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py new file mode 100644 index 00000000..7f2bb292 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class Percentage(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py b/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py new file mode 100644 index 00000000..773d7d80 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py @@ -0,0 +1,31 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class Smart(Strategy): + def calculate_middle(self): + difference = abs( + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] + ) + self.decision["choice"] = ( + self.return_choice(OutcomeKeys.ODDS) + if difference < self.settings.strategy_settings.percentage_gap + else self.return_choice(OutcomeKeys.TOTAL_USERS) + ) + + +class SmartSettings(object): + __slots__ = [ + "percentage_gap", + ] + + def __init__( + self, + percentage_gap: float = None, + ): + self.percentage_gap = percentage_gap + + def default(self): + self.percentage_gap = ( + self.percentage_gap if self.percentage_gap is not None else 20 + ) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py new file mode 100644 index 00000000..0966fe78 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py @@ -0,0 +1,99 @@ +import logging + +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import char_decision_as_index + +logger = logging.getLogger(__name__) + + +class SmartHighOdds(Strategy): + def both_odds_too_low(self) -> bool: + return ( + self.outcomes[0][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + ) + + def is_only_doubt(self) -> bool: + return ( + self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.settings.only_doubt + ) + + def log_skip(self, string) -> str: + logger.info( + string, + extra={ + "emoji": ":pushpin:", + "color": Settings.logger.color_palette.BET_GENERAL, + }, + ) + + def skip_middle(self): + if self.settings.strategy_settings.always_bet: + self.log_skip("always_bet activated") + return False, 0 + + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 + ): + self.log_skip("No bet on B") + return False, 0 + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + ): + if not self.settings.only_doubt: + self.log_skip("No bet on A") + return False, 0 + + if self.both_odds_too_low() or self.is_only_doubt(): + if self.both_odds_too_low(): + self.log_skip("Odd is too low") + elif self.is_only_doubt(): + self.log_skip("Odd is too low and only_doubt activated") + self.log_skip(f"Target odd: {self.settings.strategy_settings.target_odd}") + return True, 0 # Skip + + def calculate_sho_bet(self, index): + low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] + high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + if self.both_odds_too_low() or self.is_only_doubt(): + return 10 + elif high_odd_points <= 50: # in case no one bet + return 50 + else: + target_odd = self.settings.strategy_settings.target_odd + if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): + # don't bet too much if odds is too high + target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 + return int((low_odd_points / (target_odd - 1)) - high_odd_points) + + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["amount"] = int(self.calculate_sho_bet(index)) + + +class SmartHighOddsSettings(object): + __slots__ = [ + "target_odd", + "always_bet", + ] + + def __init__( + self, + target_odd: float = None, + always_bet: bool = None, + ): + self.target_odd = target_odd + self.always_bet = always_bet + + def default(self): + self.target_odd = self.target_odd if self.target_odd is not None else 3 + self.always_bet = self.always_bet if self.always_bet is not None else False From f54590100ade4984c7a7cf8ef579495709acc151 Mon Sep 17 00:00:00 2001 From: 1v Date: Fri, 22 Oct 2021 04:42:08 +0000 Subject: [PATCH 012/140] Move each strategy to separate files --- .../classes/entities/Strategy.py | 152 ++---------------- .../classes/entities/strategies/HighOdds.py | 6 + .../classes/entities/strategies/MostVoted.py | 6 + .../classes/entities/strategies/Percentage.py | 6 + .../classes/entities/strategies/Smart.py | 31 ++++ .../entities/strategies/SmartHighOdds.py | 99 ++++++++++++ 6 files changed, 157 insertions(+), 143 deletions(-) create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/Smart.py create mode 100644 TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py diff --git a/TwitchChannelPointsMiner/classes/entities/Strategy.py b/TwitchChannelPointsMiner/classes/entities/Strategy.py index d5230cdd..76fa2228 100644 --- a/TwitchChannelPointsMiner/classes/entities/Strategy.py +++ b/TwitchChannelPointsMiner/classes/entities/Strategy.py @@ -1,12 +1,9 @@ -import logging from enum import Enum, auto +from importlib import import_module from random import uniform -from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.utils import char_decision_as_index -logger = logging.getLogger(__name__) - class Condition(Enum): GT = auto() @@ -45,7 +42,10 @@ def __init__(self, outcomes: list, settings: object): self.settings = settings def get_instance(self): - subclass = globals()[self.settings.strategy] + strategy_module = import_module( + f".{self.settings.strategy}", package=f"{__package__}.strategies" + ) + subclass = getattr(strategy_module, self.settings.strategy) return subclass(self.outcomes, self.settings) def return_choice(self, key) -> str: @@ -140,146 +140,12 @@ def __str__(self): class StrategySettings(object): def __init__(self, strategy: Strategy = None, **kwargs): - subclass = globals()[f"{strategy}Settings"] + strategy_module = import_module( + f".{strategy}", package=f"{__package__}.strategies" + ) + subclass = getattr(strategy_module, f"{strategy}Settings") self.instance = subclass(**kwargs) self.instance.default() def get_instance(self): return self.instance - - -class SmartHighOddsSettings(object): - __slots__ = [ - "target_odd", - "always_bet", - ] - - def __init__( - self, - target_odd: float = None, - always_bet: bool = None, - ): - self.target_odd = target_odd - self.always_bet = always_bet - - def default(self): - self.target_odd = self.target_odd if self.target_odd is not None else 3 - self.always_bet = self.always_bet if self.always_bet is not None else False - - -class SmartSettings(object): - __slots__ = [ - "percentage_gap", - ] - - def __init__( - self, - percentage_gap: float = None, - ): - self.percentage_gap = percentage_gap - - def default(self): - self.percentage_gap = ( - self.percentage_gap if self.percentage_gap is not None else 20 - ) - - -class SmartHighOdds(Strategy): - def both_odds_too_low(self) -> bool: - return ( - self.outcomes[0][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - and self.outcomes[1][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - ) - - def is_only_doubt(self) -> bool: - return ( - self.outcomes[1][OutcomeKeys.ODDS] - <= self.settings.strategy_settings.target_odd - and self.settings.only_doubt - ) - - def log_skip(self, string) -> str: - logger.info( - string, - extra={ - "emoji": ":pushpin:", - "color": Settings.logger.color_palette.BET_GENERAL, - }, - ) - - def skip_middle(self): - if self.settings.strategy_settings.always_bet: - self.log_skip("always_bet activated") - return False, 0 - - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 - ): - self.log_skip("No bet on B") - return False, 0 - if ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 - ): - if not self.settings.only_doubt: - self.log_skip("No bet on A") - return False, 0 - - if self.both_odds_too_low() or self.is_only_doubt(): - if self.both_odds_too_low(): - self.log_skip("Odd is too low") - elif self.is_only_doubt(): - self.log_skip("Odd is too low and only_doubt activated") - self.log_skip(f"Target odd: {self.settings.strategy_settings.target_odd}") - return True, 0 # Skip - - def calculate_sho_bet(self, index): - low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] - high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - if self.both_odds_too_low() or self.is_only_doubt(): - return 10 - elif high_odd_points <= 50: # in case no one bet - return 50 - else: - target_odd = self.settings.strategy_settings.target_odd - if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): - # don't bet too much if odds is too high - target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 - return int((low_odd_points / (target_odd - 1)) - high_odd_points) - - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) - self.decision["amount"] = int(self.calculate_sho_bet(index)) - - -class MostVoted(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) - - -class HighOdds(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) - - -class Percentage(Strategy): - def calculate_middle(self): - self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) - - -class Smart(Strategy): - def calculate_middle(self): - difference = abs( - self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] - - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] - ) - self.decision["choice"] = ( - self.return_choice(OutcomeKeys.ODDS) - if difference < self.settings.strategy_settings.percentage_gap - else self.return_choice(OutcomeKeys.TOTAL_USERS) - ) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py b/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py new file mode 100644 index 00000000..4fdaa7a5 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/HighOdds.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class HighOdds(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py b/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py new file mode 100644 index 00000000..f455a958 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/MostVoted.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class MostVoted(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.TOTAL_USERS) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py b/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py new file mode 100644 index 00000000..7f2bb292 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/Percentage.py @@ -0,0 +1,6 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class Percentage(Strategy): + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS_PERCENTAGE) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py b/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py new file mode 100644 index 00000000..773d7d80 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/Smart.py @@ -0,0 +1,31 @@ +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy + + +class Smart(Strategy): + def calculate_middle(self): + difference = abs( + self.outcomes[0][OutcomeKeys.PERCENTAGE_USERS] + - self.outcomes[1][OutcomeKeys.PERCENTAGE_USERS] + ) + self.decision["choice"] = ( + self.return_choice(OutcomeKeys.ODDS) + if difference < self.settings.strategy_settings.percentage_gap + else self.return_choice(OutcomeKeys.TOTAL_USERS) + ) + + +class SmartSettings(object): + __slots__ = [ + "percentage_gap", + ] + + def __init__( + self, + percentage_gap: float = None, + ): + self.percentage_gap = percentage_gap + + def default(self): + self.percentage_gap = ( + self.percentage_gap if self.percentage_gap is not None else 20 + ) diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py new file mode 100644 index 00000000..0966fe78 --- /dev/null +++ b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py @@ -0,0 +1,99 @@ +import logging + +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy +from TwitchChannelPointsMiner.classes.Settings import Settings +from TwitchChannelPointsMiner.utils import char_decision_as_index + +logger = logging.getLogger(__name__) + + +class SmartHighOdds(Strategy): + def both_odds_too_low(self) -> bool: + return ( + self.outcomes[0][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + ) + + def is_only_doubt(self) -> bool: + return ( + self.outcomes[1][OutcomeKeys.ODDS] + <= self.settings.strategy_settings.target_odd + and self.settings.only_doubt + ) + + def log_skip(self, string) -> str: + logger.info( + string, + extra={ + "emoji": ":pushpin:", + "color": Settings.logger.color_palette.BET_GENERAL, + }, + ) + + def skip_middle(self): + if self.settings.strategy_settings.always_bet: + self.log_skip("always_bet activated") + return False, 0 + + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] == 0 + ): + self.log_skip("No bet on B") + return False, 0 + if ( + self.outcomes[0][OutcomeKeys.TOTAL_POINTS] == 0 + and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + ): + if not self.settings.only_doubt: + self.log_skip("No bet on A") + return False, 0 + + if self.both_odds_too_low() or self.is_only_doubt(): + if self.both_odds_too_low(): + self.log_skip("Odd is too low") + elif self.is_only_doubt(): + self.log_skip("Odd is too low and only_doubt activated") + self.log_skip(f"Target odd: {self.settings.strategy_settings.target_odd}") + return True, 0 # Skip + + def calculate_sho_bet(self, index): + low_odd_points = self.outcomes[1 - index][OutcomeKeys.TOTAL_POINTS] + high_odd_points = self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + if self.both_odds_too_low() or self.is_only_doubt(): + return 10 + elif high_odd_points <= 50: # in case no one bet + return 50 + else: + target_odd = self.settings.strategy_settings.target_odd + if self.outcomes[index][OutcomeKeys.ODDS] > (target_odd * 2): + # don't bet too much if odds is too high + target_odd = self.outcomes[index][OutcomeKeys.ODDS] / 2 + return int((low_odd_points / (target_odd - 1)) - high_odd_points) + + def calculate_middle(self): + self.decision["choice"] = self.return_choice(OutcomeKeys.ODDS) + if self.decision["choice"] is not None: + index = char_decision_as_index(self.decision["choice"]) + self.decision["amount"] = int(self.calculate_sho_bet(index)) + + +class SmartHighOddsSettings(object): + __slots__ = [ + "target_odd", + "always_bet", + ] + + def __init__( + self, + target_odd: float = None, + always_bet: bool = None, + ): + self.target_odd = target_odd + self.always_bet = always_bet + + def default(self): + self.target_odd = self.target_odd if self.target_odd is not None else 3 + self.always_bet = self.always_bet if self.always_bet is not None else False From 355c51da95212a3bd1a6d6366fe7718a0fce48cb Mon Sep 17 00:00:00 2001 From: 1v Date: Tue, 2 Nov 2021 23:28:13 +0000 Subject: [PATCH 013/140] Fix README.md --- README.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e9d7ed86..99c02e94 100644 --- a/README.md +++ b/README.md @@ -300,15 +300,16 @@ Available values are the following: You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. ### LoggerSettings -| Key | Type | Default | Description | -|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `save` | bool | True | If you want to save logs in file (suggested) | -| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | -| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | -| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | -| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | -| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | +| 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 printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | #### Color Palette Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. @@ -394,11 +395,11 @@ Here a concrete example: - - In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' ### FilterCondition -| Key | Type | Default | Description | -|------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `by` | OutcomeKeys | None | Key to apply the filter | -| `where` | Condition | None | Condition that should match for place bet | -| `value` | number | None | Value to compare | +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | Allowed values for `by` are: - `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] From e338389d03bc0375843f8903903537c562ba04c9 Mon Sep 17 00:00:00 2001 From: 1v Date: Tue, 2 Nov 2021 23:33:04 +0000 Subject: [PATCH 014/140] Fix README.md again --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 99c02e94..be114cda 100644 --- a/README.md +++ b/README.md @@ -142,23 +142,23 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - 📊 BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") - Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) - Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) - Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") - Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) - Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) - Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) - Result: {'type': 'WIN', 'won': 6531} + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") - Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) - Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) - Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 %d/%m/%y %H:%M:%S - 💰 CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) From f8d4dce81fa0f227baadff995767007a90b29e6b Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 15 Sep 2022 16:01:07 +0700 Subject: [PATCH 015/140] Add integrity request --- TwitchChannelPointsMiner/classes/Twitch.py | 36 +++++++++++++++++++++- TwitchChannelPointsMiner/constants.py | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 8b831d1c..016acac2 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -9,7 +9,9 @@ import os import random import re +import string import time +from datetime import datetime from pathlib import Path from secrets import token_hex @@ -39,7 +41,7 @@ class Twitch(object): - __slots__ = ["cookies_file", "user_agent", "twitch_login", "running"] + __slots__ = ["cookies_file", "user_agent", "twitch_login", "running", "device_id", "integrity", "integrity_expire"] def __init__(self, username, user_agent, password=None): cookies_path = os.path.join(Path().absolute(), "cookies") @@ -50,6 +52,9 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True + self.device_id = ''.join(random.choices(string.ascii_letters + string.digits, k=26)) + self.integrity = None + self.integrity_expire = 0 def login(self): if os.path.isfile(self.cookies_file) is False: @@ -231,7 +236,10 @@ def post_gql_request(self, json_data): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Integrity": self.post_integrity(), + "Device-ID": self.device_id, "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, }, ) logger.debug( @@ -244,6 +252,32 @@ def post_gql_request(self, json_data): ) return {} + # Request for Integrity Token + # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests + def post_integrity(self): + if datetime.now().timestamp() * 1000 - self.integrity_expire < 0 and self.integrity is not None: + return self.integrity + try: + response = requests.post( + GQLOperations.integrity_url, + json={}, + headers={ + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, + }, + ) + logger.debug( + f"Data: [], Status code: {response.status_code}, Content: {response.text}" + ) + self.integrity = response.json().get('token', None) + self.integrity_expire = response.json().get('expiration', 0) + return self.integrity + except requests.exceptions.RequestException as e: + logger.error(f"Error with post_integrity: {e}") + return self.integrity + def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: try: diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index c502433a..7189d9e9 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -26,6 +26,7 @@ class GQLOperations: url = "https://gql.twitch.tv/gql" + integrity_url = "https://gql.twitch.tv/integrity" WithIsStreamLiveQuery = { "operationName": "WithIsStreamLiveQuery", "extensions": { From f1106c7d0988f5adbe6f82e40026576fc085143b Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 15 Sep 2022 16:58:54 +0700 Subject: [PATCH 016/140] Formatting code --- TwitchChannelPointsMiner/classes/Twitch.py | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 016acac2..fa1af326 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -41,7 +41,15 @@ class Twitch(object): - __slots__ = ["cookies_file", "user_agent", "twitch_login", "running", "device_id", "integrity", "integrity_expire"] + __slots__ = [ + "cookies_file", + "user_agent", + "twitch_login", + "running", + "device_id", + "integrity", + "integrity_expire" + ] def __init__(self, username, user_agent, password=None): cookies_path = os.path.join(Path().absolute(), "cookies") @@ -52,7 +60,9 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True - self.device_id = ''.join(random.choices(string.ascii_letters + string.digits, k=26)) + self.device_id = ''.join( + random.choices(string.ascii_letters + string.digits, k=26) + ) self.integrity = None self.integrity_expire = 0 @@ -255,7 +265,10 @@ def post_gql_request(self, json_data): # Request for Integrity Token # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests def post_integrity(self): - if datetime.now().timestamp() * 1000 - self.integrity_expire < 0 and self.integrity is not None: + if ( + datetime.now().timestamp() * 1000 - self.integrity_expire < 0 + and self.integrity is not None + ): return self.integrity try: response = requests.post( @@ -271,8 +284,8 @@ def post_integrity(self): logger.debug( f"Data: [], Status code: {response.status_code}, Content: {response.text}" ) - self.integrity = response.json().get('token', None) - self.integrity_expire = response.json().get('expiration', 0) + self.integrity = response.json().get("token", None) + self.integrity_expire = response.json().get("expiration", 0) return self.integrity except requests.exceptions.RequestException as e: logger.error(f"Error with post_integrity: {e}") From dae9c709862ea386e866ea83ef55bc0c70c5999f Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:09:25 +0700 Subject: [PATCH 017/140] dynamically update client version --- TwitchChannelPointsMiner/classes/Twitch.py | 31 ++++++++++++++++++++-- TwitchChannelPointsMiner/constants.py | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index fa1af326..4eb87304 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -30,7 +30,7 @@ Settings, ) from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants import CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.constants import CLIENT_ID, CLIENT_VERSION, URL, GQLOperations from TwitchChannelPointsMiner.utils import ( _millify, create_chunks, @@ -48,7 +48,9 @@ class Twitch(object): "running", "device_id", "integrity", - "integrity_expire" + "integrity_expire", + "client_version", + "twilight_build_id_pattern" ] def __init__(self, username, user_agent, password=None): @@ -65,6 +67,10 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 + self.client_version = CLIENT_VERSION + self.twilight_build_id_pattern = re.compile( + r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" + ) def login(self): if os.path.isfile(self.cookies_file) is False: @@ -247,6 +253,7 @@ def post_gql_request(self, json_data): "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), + "Client-Version": self.client_version, "Device-ID": self.device_id, "User-Agent": self.user_agent, "X-Device-Id": self.device_id, @@ -277,6 +284,7 @@ def post_integrity(self): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, @@ -291,6 +299,25 @@ def post_integrity(self): logger.error(f"Error with post_integrity: {e}") return self.integrity + def update_client_version(self): + try: + response = requests.get(URL) + if response.status_code != 200: + logger.debug( + f"Error with update_client_version: {response.status_code}" + ) + return self.client_version + matcher = re.search(self.twilight_build_id_pattern, response.text) + if not matcher: + logger.debug("Error with update_client_version: no match") + return self.client_version + self.client_version = matcher.group(1) + logger.debug(f"Client version: {self.client_version}") + return self.client_version + except requests.exceptions.RequestException as e: + logger.error(f"Error with update_client_version: {e}") + return self.client_version + def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: try: diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 7189d9e9..98a14c71 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,6 +5,7 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" +CLIENT_VERSION = "ef928475-9403-42f2-8a34-55784bd08e16" USER_AGENTS = { "Windows": { From 8594d54f08eec723cc4bc238aa46412f4283df71 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:36:15 +0700 Subject: [PATCH 018/140] Add generated client session id --- TwitchChannelPointsMiner/classes/Twitch.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 4eb87304..ab9295d4 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -49,6 +49,7 @@ class Twitch(object): "device_id", "integrity", "integrity_expire", + "client_session", "client_version", "twilight_build_id_pattern" ] @@ -67,6 +68,9 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 + self.client_session = ''.join( + random.choices('0123456789abcdef', k=16) + ) self.client_version = CLIENT_VERSION self.twilight_build_id_pattern = re.compile( r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" @@ -253,8 +257,8 @@ def post_gql_request(self, json_data): "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), + "Client-Session-Id": self.client_session, "Client-Version": self.client_version, - "Device-ID": self.device_id, "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, @@ -284,6 +288,7 @@ def post_integrity(self): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Session-Id": self.client_session, "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, From 186ea09e042b991c1f674df0c884fe6976cf2590 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:39:12 +0700 Subject: [PATCH 019/140] Regenerate Integrity Token 5 min before expire --- TwitchChannelPointsMiner/classes/Twitch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index ab9295d4..b517f0c0 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -275,9 +275,10 @@ def post_gql_request(self, json_data): # Request for Integrity Token # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests + # Regenerate Integrity Token 5 minutes before expire def post_integrity(self): if ( - datetime.now().timestamp() * 1000 - self.integrity_expire < 0 + self.integrity_expire - datetime.now().timestamp() * 1000 > 5 * 60 * 1000 and self.integrity is not None ): return self.integrity From d25228249cdd96c5b33fb4512c7e3f8cbda9a28a Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 19:07:41 +0700 Subject: [PATCH 020/140] Switch from random.choices to secrets.choice --- TwitchChannelPointsMiner/classes/Twitch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index b517f0c0..c6b148c1 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -13,7 +13,7 @@ import time from datetime import datetime from pathlib import Path -from secrets import token_hex +from secrets import choice, token_hex import requests @@ -63,8 +63,8 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True - self.device_id = ''.join( - random.choices(string.ascii_letters + string.digits, k=26) + self.device_id = "".join( + choice(string.ascii_letters + string.digits) for _ in range(32) ) self.integrity = None self.integrity_expire = 0 From 3fcd17ccd36e3fed97cbc8d9314c34544f1af40b Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 19:08:01 +0700 Subject: [PATCH 021/140] Switch from random.choices to secrets.token_hex --- TwitchChannelPointsMiner/classes/Twitch.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c6b148c1..bd092aa4 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -68,9 +68,7 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 - self.client_session = ''.join( - random.choices('0123456789abcdef', k=16) - ) + self.client_session = token_hex(16) self.client_version = CLIENT_VERSION self.twilight_build_id_pattern = re.compile( r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" From e441551f62da94230a0a2ee71198d0682fd06c0f Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Mon, 19 Sep 2022 09:33:13 +0700 Subject: [PATCH 022/140] Formatting code --- TwitchChannelPointsMiner/classes/Twitch.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index bd092aa4..867c2e1b 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -30,7 +30,12 @@ Settings, ) from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants import CLIENT_ID, CLIENT_VERSION, URL, GQLOperations +from TwitchChannelPointsMiner.constants import ( + CLIENT_ID, + CLIENT_VERSION, + URL, + GQLOperations, +) from TwitchChannelPointsMiner.utils import ( _millify, create_chunks, @@ -51,7 +56,7 @@ class Twitch(object): "integrity_expire", "client_session", "client_version", - "twilight_build_id_pattern" + "twilight_build_id_pattern", ] def __init__(self, username, user_agent, password=None): From 8bbbbc0a52f60452c9c4644045dbd29b1819684d Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 22 Sep 2022 09:33:33 +0700 Subject: [PATCH 023/140] update client version --- TwitchChannelPointsMiner/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 98a14c71..82384833 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,7 +5,7 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" -CLIENT_VERSION = "ef928475-9403-42f2-8a34-55784bd08e16" +CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" USER_AGENTS = { "Windows": { From 896515fefee285f9ca27e42fcdca284e4cccefa4 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 22 Sep 2022 09:34:10 +0700 Subject: [PATCH 024/140] Fix bug when client version update by update client version on every requests --- 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 867c2e1b..c2867a14 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -261,7 +261,7 @@ def post_gql_request(self, json_data): "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), "Client-Session-Id": self.client_session, - "Client-Version": self.client_version, + "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, From 7170fb45a3e0bbc267c4ec6c76ff4e90ec040dc1 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 15 Sep 2022 16:01:07 +0700 Subject: [PATCH 025/140] Add integrity request --- TwitchChannelPointsMiner/classes/Twitch.py | 36 +++++++++++++++++++++- TwitchChannelPointsMiner/constants.py | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c27d8839..3aebff7a 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -9,7 +9,9 @@ import os import random import re +import string import time +from datetime import datetime from pathlib import Path from secrets import token_hex @@ -39,7 +41,7 @@ class Twitch(object): - __slots__ = ["cookies_file", "user_agent", "twitch_login", "running"] + __slots__ = ["cookies_file", "user_agent", "twitch_login", "running", "device_id", "integrity", "integrity_expire"] def __init__(self, username, user_agent, password=None): cookies_path = os.path.join(Path().absolute(), "cookies") @@ -50,6 +52,9 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True + self.device_id = ''.join(random.choices(string.ascii_letters + string.digits, k=26)) + self.integrity = None + self.integrity_expire = 0 def login(self): if os.path.isfile(self.cookies_file) is False: @@ -231,7 +236,10 @@ def post_gql_request(self, json_data): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Integrity": self.post_integrity(), + "Device-ID": self.device_id, "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, }, ) logger.debug( @@ -244,6 +252,32 @@ def post_gql_request(self, json_data): ) return {} + # Request for Integrity Token + # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests + def post_integrity(self): + if datetime.now().timestamp() * 1000 - self.integrity_expire < 0 and self.integrity is not None: + return self.integrity + try: + response = requests.post( + GQLOperations.integrity_url, + json={}, + headers={ + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + "User-Agent": self.user_agent, + "X-Device-Id": self.device_id, + }, + ) + logger.debug( + f"Data: [], Status code: {response.status_code}, Content: {response.text}" + ) + self.integrity = response.json().get('token', None) + self.integrity_expire = response.json().get('expiration', 0) + return self.integrity + except requests.exceptions.RequestException as e: + logger.error(f"Error with post_integrity: {e}") + return self.integrity + def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: try: diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index c502433a..7189d9e9 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -26,6 +26,7 @@ class GQLOperations: url = "https://gql.twitch.tv/gql" + integrity_url = "https://gql.twitch.tv/integrity" WithIsStreamLiveQuery = { "operationName": "WithIsStreamLiveQuery", "extensions": { From 5900a3449989914ac487c4e002bb5846608ee46a Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 15 Sep 2022 16:58:54 +0700 Subject: [PATCH 026/140] Formatting code --- TwitchChannelPointsMiner/classes/Twitch.py | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 3aebff7a..1b4b5048 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -41,7 +41,15 @@ class Twitch(object): - __slots__ = ["cookies_file", "user_agent", "twitch_login", "running", "device_id", "integrity", "integrity_expire"] + __slots__ = [ + "cookies_file", + "user_agent", + "twitch_login", + "running", + "device_id", + "integrity", + "integrity_expire" + ] def __init__(self, username, user_agent, password=None): cookies_path = os.path.join(Path().absolute(), "cookies") @@ -52,7 +60,9 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True - self.device_id = ''.join(random.choices(string.ascii_letters + string.digits, k=26)) + self.device_id = ''.join( + random.choices(string.ascii_letters + string.digits, k=26) + ) self.integrity = None self.integrity_expire = 0 @@ -255,7 +265,10 @@ def post_gql_request(self, json_data): # Request for Integrity Token # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests def post_integrity(self): - if datetime.now().timestamp() * 1000 - self.integrity_expire < 0 and self.integrity is not None: + if ( + datetime.now().timestamp() * 1000 - self.integrity_expire < 0 + and self.integrity is not None + ): return self.integrity try: response = requests.post( @@ -271,8 +284,8 @@ def post_integrity(self): logger.debug( f"Data: [], Status code: {response.status_code}, Content: {response.text}" ) - self.integrity = response.json().get('token', None) - self.integrity_expire = response.json().get('expiration', 0) + self.integrity = response.json().get("token", None) + self.integrity_expire = response.json().get("expiration", 0) return self.integrity except requests.exceptions.RequestException as e: logger.error(f"Error with post_integrity: {e}") From 589324a26c7778bf46a2030716ef42f95d17ee38 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:09:25 +0700 Subject: [PATCH 027/140] dynamically update client version --- TwitchChannelPointsMiner/classes/Twitch.py | 31 ++++++++++++++++++++-- TwitchChannelPointsMiner/constants.py | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 1b4b5048..31a35301 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -30,7 +30,7 @@ Settings, ) from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants import CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.constants import CLIENT_ID, CLIENT_VERSION, URL, GQLOperations from TwitchChannelPointsMiner.utils import ( _millify, create_chunks, @@ -48,7 +48,9 @@ class Twitch(object): "running", "device_id", "integrity", - "integrity_expire" + "integrity_expire", + "client_version", + "twilight_build_id_pattern" ] def __init__(self, username, user_agent, password=None): @@ -65,6 +67,10 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 + self.client_version = CLIENT_VERSION + self.twilight_build_id_pattern = re.compile( + r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" + ) def login(self): if os.path.isfile(self.cookies_file) is False: @@ -247,6 +253,7 @@ def post_gql_request(self, json_data): "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), + "Client-Version": self.client_version, "Device-ID": self.device_id, "User-Agent": self.user_agent, "X-Device-Id": self.device_id, @@ -277,6 +284,7 @@ def post_integrity(self): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, @@ -291,6 +299,25 @@ def post_integrity(self): logger.error(f"Error with post_integrity: {e}") return self.integrity + def update_client_version(self): + try: + response = requests.get(URL) + if response.status_code != 200: + logger.debug( + f"Error with update_client_version: {response.status_code}" + ) + return self.client_version + matcher = re.search(self.twilight_build_id_pattern, response.text) + if not matcher: + logger.debug("Error with update_client_version: no match") + return self.client_version + self.client_version = matcher.group(1) + logger.debug(f"Client version: {self.client_version}") + return self.client_version + except requests.exceptions.RequestException as e: + logger.error(f"Error with update_client_version: {e}") + return self.client_version + def send_minute_watched_events(self, streamers, priority, chunk_size=3): while self.running: try: diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 7189d9e9..98a14c71 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,6 +5,7 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" +CLIENT_VERSION = "ef928475-9403-42f2-8a34-55784bd08e16" USER_AGENTS = { "Windows": { From c9b658920c33b5d165cc4cf36e0c13312383aa6a Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:36:15 +0700 Subject: [PATCH 028/140] Add generated client session id --- TwitchChannelPointsMiner/classes/Twitch.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 31a35301..fb67e0bb 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -49,6 +49,7 @@ class Twitch(object): "device_id", "integrity", "integrity_expire", + "client_session", "client_version", "twilight_build_id_pattern" ] @@ -67,6 +68,9 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 + self.client_session = ''.join( + random.choices('0123456789abcdef', k=16) + ) self.client_version = CLIENT_VERSION self.twilight_build_id_pattern = re.compile( r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" @@ -253,8 +257,8 @@ def post_gql_request(self, json_data): "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), + "Client-Session-Id": self.client_session, "Client-Version": self.client_version, - "Device-ID": self.device_id, "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, @@ -284,6 +288,7 @@ def post_integrity(self): headers={ "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", "Client-Id": CLIENT_ID, + "Client-Session-Id": self.client_session, "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, From 52c49637d5c03117b1c6f14e5a24dd7438a898b7 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 12:39:12 +0700 Subject: [PATCH 029/140] Regenerate Integrity Token 5 min before expire --- TwitchChannelPointsMiner/classes/Twitch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index fb67e0bb..b84bb99a 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -275,9 +275,10 @@ def post_gql_request(self, json_data): # Request for Integrity Token # Twitch needs Authorization, Client-Id, X-Device-Id to generate JWT which is used for authorize gql requests + # Regenerate Integrity Token 5 minutes before expire def post_integrity(self): if ( - datetime.now().timestamp() * 1000 - self.integrity_expire < 0 + self.integrity_expire - datetime.now().timestamp() * 1000 > 5 * 60 * 1000 and self.integrity is not None ): return self.integrity From bb777194f64597ebbab4785a6777f0a4e01251c4 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 19:07:41 +0700 Subject: [PATCH 030/140] Switch from random.choices to secrets.choice --- TwitchChannelPointsMiner/classes/Twitch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index b84bb99a..7dade125 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -13,7 +13,7 @@ import time from datetime import datetime from pathlib import Path -from secrets import token_hex +from secrets import choice, token_hex import requests @@ -63,8 +63,8 @@ def __init__(self, username, user_agent, password=None): CLIENT_ID, username, self.user_agent, password=password ) self.running = True - self.device_id = ''.join( - random.choices(string.ascii_letters + string.digits, k=26) + self.device_id = "".join( + choice(string.ascii_letters + string.digits) for _ in range(32) ) self.integrity = None self.integrity_expire = 0 From 0d8298710abe83cad8b0c4850cff4aef8483c9d4 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Sat, 17 Sep 2022 19:08:01 +0700 Subject: [PATCH 031/140] Switch from random.choices to secrets.token_hex --- TwitchChannelPointsMiner/classes/Twitch.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 7dade125..2fa21959 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -68,9 +68,7 @@ def __init__(self, username, user_agent, password=None): ) self.integrity = None self.integrity_expire = 0 - self.client_session = ''.join( - random.choices('0123456789abcdef', k=16) - ) + self.client_session = token_hex(16) self.client_version = CLIENT_VERSION self.twilight_build_id_pattern = re.compile( r"window\.__twilightBuildID=\"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12})\";" From e14becf4fed2b08c8660e1997dc4e59f3e8b722b Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Mon, 19 Sep 2022 09:33:13 +0700 Subject: [PATCH 032/140] Formatting code --- TwitchChannelPointsMiner/classes/Twitch.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 2fa21959..e97f551e 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -30,7 +30,12 @@ Settings, ) from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants import CLIENT_ID, CLIENT_VERSION, URL, GQLOperations +from TwitchChannelPointsMiner.constants import ( + CLIENT_ID, + CLIENT_VERSION, + URL, + GQLOperations, +) from TwitchChannelPointsMiner.utils import ( _millify, create_chunks, @@ -51,7 +56,7 @@ class Twitch(object): "integrity_expire", "client_session", "client_version", - "twilight_build_id_pattern" + "twilight_build_id_pattern", ] def __init__(self, username, user_agent, password=None): From 7097cd6b0e01eca14f78439cb4891d1ceb23f9c9 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 22 Sep 2022 09:33:33 +0700 Subject: [PATCH 033/140] update client version --- TwitchChannelPointsMiner/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 98a14c71..82384833 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,7 +5,7 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" -CLIENT_VERSION = "ef928475-9403-42f2-8a34-55784bd08e16" +CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" USER_AGENTS = { "Windows": { From 952d2defc12f58d2cad6cd8a21eb911250f6e3c4 Mon Sep 17 00:00:00 2001 From: Kittipos Wajakajornrit Date: Thu, 22 Sep 2022 09:34:10 +0700 Subject: [PATCH 034/140] Fix bug when client version update by update client version on every requests --- 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 e97f551e..3d0d3784 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -261,7 +261,7 @@ def post_gql_request(self, json_data): "Client-Id": CLIENT_ID, "Client-Integrity": self.post_integrity(), "Client-Session-Id": self.client_session, - "Client-Version": self.client_version, + "Client-Version": self.update_client_version(), "User-Agent": self.user_agent, "X-Device-Id": self.device_id, }, From c425c805b04ad9e02023fecdac3285d24cb09206 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 13 Oct 2022 21:44:51 +0300 Subject: [PATCH 035/140] new CLIENT_ID (twitch app?) --- TwitchChannelPointsMiner/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 82384833..711db889 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -3,7 +3,7 @@ IRC = "irc.chat.twitch.tv" IRC_PORT = 6667 WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" -CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" +CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" From 789be8e36934c00981a7e1ec4da959773cad3ed8 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:28:11 +0300 Subject: [PATCH 036/140] enableAnalytics.py file toggles Analytics --- .../TwitchChannelPointsMiner.py | 46 ++++++++++++------- .../classes/WebSocketsPool.py | 29 ++++++++---- TwitchChannelPointsMiner/logger.py | 2 +- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index b20334fa..dbf9f639 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -11,7 +11,6 @@ from datetime import datetime from pathlib import Path -from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer from TwitchChannelPointsMiner.classes.Chat import ChatPresence, ThreadChat from TwitchChannelPointsMiner.classes.entities.PubsubTopic import PubsubTopic from TwitchChannelPointsMiner.classes.entities.Streamer import ( @@ -32,6 +31,17 @@ set_default_settings, ) +disableAnalytics = False + +try: + import enableAnalytics +except ImportError: + disableAnalytics = True + +# Analytics switch +if disableAnalytics is False: + from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer + # Suppress: # - chardet.charsetprober - [feed] # - chardet.charsetprober - [get_confidence] @@ -79,8 +89,10 @@ def __init__( # Default values for all streamers streamer_settings: StreamerSettings = StreamerSettings(), ): - Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) - Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) + # Analytics switch + if disableAnalytics is False: + Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) + Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) self.username = username @@ -126,19 +138,21 @@ def __init__( for sign in [signal.SIGINT, signal.SIGSEGV, signal.SIGTERM]: signal.signal(sign, self.end) - def analytics( - self, - host: str = "127.0.0.1", - port: int = 5000, - refresh: int = 5, - days_ago: int = 7, - ): - http_server = AnalyticsServer( - host=host, port=port, refresh=refresh, days_ago=days_ago - ) - http_server.daemon = True - http_server.name = "Analytics Thread" - http_server.start() + # Analytics switch + if disableAnalytics is False: + def analytics( + self, + host: str = "127.0.0.1", + port: int = 5000, + refresh: int = 5, + days_ago: int = 7, + ): + http_server = AnalyticsServer( + host=host, port=port, refresh=refresh, days_ago=days_ago + ) + http_server.daemon = True + http_server.name = "Analytics Thread" + http_server.start() def mine( self, diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 43ad9e86..472677fe 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -17,6 +17,13 @@ internet_connection_available, ) +disableAnalytics = False + +try: + import enableAnalytics +except ImportError: + disableAnalytics = True + logger = logging.getLogger(__name__) @@ -170,7 +177,7 @@ def on_message(ws, message): ws.last_message_timestamp = message.timestamp ws.last_message_type_channel = message.identifier - + streamer_index = get_streamer_index(ws.streamers, message.channel_id) if streamer_index != -1: try: @@ -178,11 +185,13 @@ def on_message(ws, message): if message.type in ["points-earned", "points-spent"]: balance = message.data["balance"]["balance"] ws.streamers[streamer_index].channel_points = balance - ws.streamers[streamer_index].persistent_series( - event_type=message.data["point_gain"]["reason_code"] - if message.type == "points-earned" - else "Spent" - ) + # Analytics switch + if disableAnalytics is False: + ws.streamers[streamer_index].persistent_series( + event_type=message.data["point_gain"]["reason_code"] + if message.type == "points-earned" + else "Spent" + ) if message.type == "points-earned": earned = message.data["point_gain"]["total_points"] @@ -198,9 +207,11 @@ def on_message(ws, message): ws.streamers[streamer_index].update_history( reason_code, earned ) - ws.streamers[streamer_index].persistent_annotations( - reason_code, f"+{earned} - {reason_code}" - ) + # Analytics switch + if disableAnalytics is False: + ws.streamers[streamer_index].persistent_annotations( + reason_code, f"+{earned} - {reason_code}" + ) elif message.type == "claim-available": ws.twitch.claim_bonus( ws.streamers[streamer_index], diff --git a/TwitchChannelPointsMiner/logger.py b/TwitchChannelPointsMiner/logger.py index 45ec3252..11ee5d2d 100644 --- a/TwitchChannelPointsMiner/logger.py +++ b/TwitchChannelPointsMiner/logger.py @@ -68,7 +68,7 @@ class LoggerSettings: "color_palette", "auto_clear", "telegram", - "discord", + "discord" ] def __init__( From 0bac99a0f1b0cf3e2d1390dae066fdbae63b9bf3 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:29:23 +0300 Subject: [PATCH 037/140] remove or rename to disable Analytics --- enableAnalytics.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 enableAnalytics.py diff --git a/enableAnalytics.py b/enableAnalytics.py new file mode 100644 index 00000000..2283d99e --- /dev/null +++ b/enableAnalytics.py @@ -0,0 +1,4 @@ +# Significantly reduces memory consumption and saves some disk space. +# Remove this file if you don't need Analytics. Or rename it to something different, like "disableAnalytics.py". +# To enable Analytics back - just create this file again. Or rename it back to "enableAnalytics.py". +# This file can be empty. \ No newline at end of file From b815d9278e2f2585dd82586fe39e39e2509b8a11 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:20:55 +0300 Subject: [PATCH 038/140] Update README.md enableAnalytics.py file toggles Analytics --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 74352640..f095d874 100644 --- a/README.md +++ b/README.md @@ -571,6 +571,14 @@ The chart provides some annotation to handle the prediction and watch strike eve On each (x, y) points Its present a tooltip that show points, date time and reason of points gained / lost. This web page was just a funny idea, and it is not intended to use for a professional usage. If you want you can toggle the dark theme with the dedicated checkbox. +### `enableAnalytics.py` file in the main directory toggles Analytics +Disabling Analytics significantly reduces memory consumption and saves some disk space. + +- Remove this file if you don't need Analytics. Or rename it to something different, like `disableAnalytics.py`. +- To enable Analytics back - just create this file again. Or rename it back to `enableAnalytics.py`. + +This file can be empty. + | Light theme | Dark theme | | ----------- | ---------- | | ![Light theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-light.png) | ![Dark theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-dark.png) | From 5ca8054bcbc960f026ed545471eb6ddd9deb6ec1 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 12:54:04 +0300 Subject: [PATCH 039/140] BETTER_FLAG than camelCase (best practices) --- enableAnalytics.py => ENABLE_ANALYTICS.py | 4 +- README.md | 242 +++++++++++------- .../TwitchChannelPointsMiner.py | 12 +- .../classes/WebSocketsPool.py | 10 +- 4 files changed, 158 insertions(+), 110 deletions(-) rename enableAnalytics.py => ENABLE_ANALYTICS.py (63%) diff --git a/enableAnalytics.py b/ENABLE_ANALYTICS.py similarity index 63% rename from enableAnalytics.py rename to ENABLE_ANALYTICS.py index 2283d99e..9ec9eca0 100644 --- a/enableAnalytics.py +++ b/ENABLE_ANALYTICS.py @@ -1,4 +1,4 @@ # Significantly reduces memory consumption and saves some disk space. -# Remove this file if you don't need Analytics. Or rename it to something different, like "disableAnalytics.py". -# To enable Analytics back - just create this file again. Or rename it back to "enableAnalytics.py". +# Remove this file if you don't need Analytics. Or rename it to something different, like "DISABLE_ANALYTICS.py". +# To enable Analytics back - just create this file again. Or rename it back to "ENABLE_ANALYTICS.py". # This file can be empty. \ No newline at end of file diff --git a/README.md b/README.md index f095d874..2ba087c7 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@

**Credits** -- Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner -- ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ + +- Main idea: +- ~~Bet system (Selenium): ~~ > A simple script that will watch a stream for you and earn the channel points. @@ -19,11 +20,14 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-points-guide) ## 📢 Help wanted + Currently, we have a lot of PRs requests opened, but the time to test and improve It's less and less. If you want to help the community and the project, please test the following PRs and give us feedback: + - [Add SMART_HIGH_ODDS strategy #172](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/172) - [Add support for arbitrary filter functions #336](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/336) # README Contents + 1. 🤝 [Community](#community) 2. 🚀 [Main differences from the original repository](#main-differences-from-the-original-repository) 3. 🧾 [Logs feature](#logs-feature) @@ -48,8 +52,8 @@ Currently, we have a lot of PRs requests opened, but the time to test and improv 9. 📱 [Termux](#termux) 10. ⚠️ [Disclaimer](#disclaimer) - ## Community + If you have any type of issue, need help, or want to suggest a new feature, please open a GitHub Issue. Don't reach 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 with this project, please leave a star 🌟 and share it with your friends! 😎 @@ -66,7 +70,7 @@ If you want to offer me a coffee, I would be grateful ❤️ If you have any issues or you want to contribute, you are welcome! But please before read the [CONTRIBUTING.md](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/CONTRIBUTING.md) file. -## Main differences from the original repository: +## Main differences from the original repository - Improve the logging - Emoji, colors, file and soo on - Final report with all the data @@ -80,7 +84,9 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner - Join IRC Chat for increase watch-time and get StreamElements points [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) ## Logs feature + ### Full logs + ``` %d/%m/%y %H:%M:%S - INFO - [run]: 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m/%y %H:%M:%S - INFO - [run]: 🤓 Loading data for x streamers. Please wait ... @@ -111,7 +117,9 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - INFO - [update_raid]: 🎭 Joining raid from Streamer(username=streamer-username, channel_id=0000000, channel_points=64398) to another-username! %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +250 → Streamer(username=streamer-username, channel_id=0000000, channel_points=6845) - Reason: RAID. ``` + ### Less logs + ``` %d/%m %H:%M:%S - 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m %H:%M:%S - 🤓 Loading data for 13 streamers. Please wait ... @@ -140,7 +148,9 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m %H:%M:%S - 🎭 Joining raid from streamer-username (xxx points) to another-username! %d/%m %H:%M:%S - 🚀 +250 → streamer-username (xxx points) - Reason: RAID. ``` -### Final report: + +### Final report + ``` %d/%m/%y %H:%M:%S - 🛑 End session 'f738d438-cdbc-4cd5-90c4-1517576f1299' %d/%m/%y %H:%M:%S - 📄 Logs file: /.../path/Twitch-Channel-Points-Miner-v2/logs/username.timestamp.log @@ -148,23 +158,23 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - 📊 BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") - Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) - Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) - Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") - Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) - Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) - Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) - Result: {'type': 'WIN', 'won': 6531} + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") - Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) - Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) - Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 %d/%m/%y %H:%M:%S - 💰 CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) @@ -176,8 +186,10 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %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: +## How to use + First of all please create a run.py file. You can just copy [example.py](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. + ```python # -*- coding: utf-8 -*- @@ -274,7 +286,9 @@ twitch_miner.mine( followers_order=FollowersOrder.ASC # Sort the followers list by follow date. ASC or DESC ) ``` + You can also use all the default values except for your username obv. Short version: + ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.classes.Settings import FollowersOrder @@ -283,7 +297,9 @@ twitch_miner.mine(["streamer1", "streamer2"]) twitch_miner.mine(followers=True, followers_order=FollowersOrder.ASC) # Automatic use the followers list OR twitch_miner.mine(["streamer1", "streamer2"], followers=True, followers_order=FollowersOrder.DESC) # Mixed ``` + If you follow so many streamers on Twitch, but you don't want to mine points for all of them, you can blacklist the users with the `blacklist` keyword. [#94](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/94) + ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -291,8 +307,10 @@ twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) # Blacklist exa ``` ### By cloning the repository + 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` . 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 @@ -303,7 +321,9 @@ pip install -r requirements.txt Start mining! `python run.py` 🥳 ### pip + Install the package via pip, you will find a stable version - maybe a different version from the master branch. + - `pip install Twitch-Channel-Points-Miner-v2` - Exceute the run.py file `python run.py` 🥳 @@ -341,6 +361,7 @@ services: ``` Example with docker run: + ```sh docker run \ -v $(pwd)/analytics:/usr/src/app/analytics \ @@ -354,6 +375,7 @@ docker run \ `$(pwd)` Could not work on Windows (cmd), please use the absolute path instead, like: `/path/of/your/cookies:/usr/src/app/cookies`. If you don't mount the volume for the analytics (or cookies or logs) folder, the folder will be automatically created on the Docker container, and you will lose all the data when it is stopped. If you don't have a cookie or It's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like + ```sh docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tkdalex/twitch-channel-points-miner-v2 ``` @@ -362,43 +384,49 @@ docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tk docker run --name user2-v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 tkdalex/twitch-channel-points-miner-v2 ``` -About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). +About the _Docker_ version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). ### Limits + > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` you can choose to download the followers sorted by follow date (ASC or DESC). ## Settings + Most of the settings are self-explained and are commented on in the example. You can watch only two streamers per time. With `priority` settings, you can select which streamers watch by use priority. You can use an array of priority or single item. I suggest using at least one priority from `ORDER`, `POINTS_ASCENDING`, `POINTS_DESCEDING` because, for example, If you set only `STREAK` after catch all watch streak, the script will stop to watch streamers. Available values are the following: - - `STREAK` - Catch the watch streak from all streamers - - `DROPS` - Claim all drops from streamers with drops tags enabled - - `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) - - `ORDER` - Following the order of the list - - `POINTS_ASCENDING` - On top the streamers with the lowest points - - `POINTS_DESCEDING` - On top the streamers with the highest points + +- `STREAK` - Catch the watch streak from all streamers +- `DROPS` - Claim all drops from streamers with drops tags enabled +- `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) +- `ORDER` - Following the order of the list +- `POINTS_ASCENDING` - On top the streamers with the lowest points +- `POINTS_DESCEDING` - On top the streamers with the highest points You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. ### LoggerSettings -| Key | Type | Default | Description | -|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `save` | bool | True | If you want to save logs in file (suggested) | -| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | -| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | -| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | -| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | -| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | -| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | + +| 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 printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | | `telegram` | Telegram | None | (Optional) Receive Telegram updates for multiple events list [#233](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/233) | | `discord` | Discord | None | (Optional) Receive Discord updates for multiple events list [#320](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/320) | #### Color Palette + Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. You can change the colors of all `Events` enum class. The colors allowed are all the Fore color from Colorama: `BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.` The script was developed to handle all the human error, lower-case upper case and more, but I want to suggest using the following code-style + ```python from colorama import Fore ColorPalette( @@ -418,17 +446,18 @@ ColorPalette( ``` #### Telegram + If you want to receive logs update on Telegram initiate a new Telegram class, else leave omit this parameter or set as None. + 1. Create a bot with [BotFather](https://t.me/botfather) 2. Get you `chat_id` with [GiveChatId](https://t.me/GiveChatId_Bot) -| Key | Type | Default | Description | -|----------------------- |----------------- |--------- |------------------------------------------------------------------- | -| `chat_id` | int | | Chat ID to send messages @GiveChatId | -| `token` | string | | Telegram API token @BotFather | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | -| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | - +| Key | Type | Default | Description | +|----------------------- |----------------- |--------- |------------------------------------------------------------------- | +| `chat_id` | int | | Chat ID to send messages @GiveChatId | +| `token` | string | | Telegram API token @BotFather | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | +| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | ```python Telegram( @@ -440,7 +469,9 @@ Telegram( ``` #### Discord + If you want to receive log updates on Discord initialize a new Discord class, else leave omit this parameter or set it as None [YT Video](https://www.youtube.com/watch?v=fKksxz2Gdnc) + 1. Go to the Server you want to receive updates 2. Click "Edit Channel" 3. Click "Integrations" @@ -449,11 +480,10 @@ If you want to receive log updates on Discord initialize a new Discord class, el 6. Name it if you want 7. Click on "Copy Webhook URL" - -| Key | Type | Default | Description | -|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | -| `webhook_api` | string | | Discord webhook URL | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | +| Key | Type | Default | Description | +|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | +| `webhook_api` | string | | Discord webhook URL | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | ```python Discord( @@ -462,51 +492,54 @@ Discord( ) ``` - #### Events - - `STREAMER_ONLINE` - - `STREAMER_OFFLINE` - - `GAIN_FOR_RAID` - - `GAIN_FOR_CLAIM` - - `GAIN_FOR_WATCH` - - `BET_WIN` - - `BET_LOSE` - - `BET_REFUND` - - `BET_FILTERS` - - `BET_GENERAL` - - `BET_FAILED` - - `BET_START` - - `BONUS_CLAIM` - - `JOIN_RAID` - - `DROP_CLAIM` - - `DROP_STATUS` + +- `STREAMER_ONLINE` +- `STREAMER_OFFLINE` +- `GAIN_FOR_RAID` +- `GAIN_FOR_CLAIM` +- `GAIN_FOR_WATCH` +- `BET_WIN` +- `BET_LOSE` +- `BET_REFUND` +- `BET_FILTERS` +- `BET_GENERAL` +- `BET_FAILED` +- `BET_START` +- `BONUS_CLAIM` +- `JOIN_RAID` +- `DROP_CLAIM` +- `DROP_STATUS` ### StreamerSettings -| Key | Type | Default | Description | -|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | -| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | -| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | -| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | -| `bet` | BetSettings | | Rules to follow for the bet | -| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | + +| Key | Type | Default | Description | +|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `bet` | BetSettings | | Rules to follow for the bet | +| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | Allowed values for `chat` are: + - `ALWAYS` Join in IRC chat and never leave - `NEVER` Never join IRC chat - `ONLINE` Partecipate to IRC chat if the streamer is online (leave if offline) - `OFFLINE` Partecipate to IRC chat if the streamer is offline (leave if online) ### BetSettings -| Key | Type | Default | Description | -|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `strategy` | Strategy | SMART | Choose your strategy! See 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) | -| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | -| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | + +| 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) | +| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | +| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | #### Bet strategy @@ -526,14 +559,17 @@ Here a concrete example: - **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' + ### FilterCondition -| Key | Type | Default | Description | -|------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `by` | OutcomeKeys | None | Key to apply the filter | -| `where` | Condition | None | Condition that should match for place bet | -| `value` | number | None | Value to compare | + +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | Allowed values for `by` are: + - `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] - `ODDS_PERCENTAGE` (no sum) [Doesn't make sense to sum odds] - `ODDS` (no sum) [Doesn't make sense to sum odds] @@ -546,6 +582,7 @@ 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 is greater than 200 `FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.GT, value=200)` - If you want to place the bet ONLY if the winning odd of your decision is greater than or equal to 1.3 @@ -566,16 +603,18 @@ Here's a concrete example. Let's suppose we have a bet that is opened with a tim - **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened) ## Analytics + We have recently introduced a little frontend where you can show with a chart you points trend. The script will spawn a Flask web-server on your machine where you can select binding address and port. The chart provides some annotation to handle the prediction and watch strike events. Usually annotation are used to notice big increase / decrease of points. If you want to can disable annotations. On each (x, y) points Its present a tooltip that show points, date time and reason of points gained / lost. This web page was just a funny idea, and it is not intended to use for a professional usage. If you want you can toggle the dark theme with the dedicated checkbox. -### `enableAnalytics.py` file in the main directory toggles Analytics +### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics + Disabling Analytics significantly reduces memory consumption and saves some disk space. -- Remove this file if you don't need Analytics. Or rename it to something different, like `disableAnalytics.py`. -- To enable Analytics back - just create this file again. Or rename it back to `enableAnalytics.py`. +- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. +- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. This file can be empty. @@ -585,6 +624,7 @@ This file can be empty. For use this feature just call the `analytics` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) The chart will be autofreshed each `refresh` minutes. If you want to connect from one to second machine that have that webpanel you have to use `0.0.0.0` instead of `127.0.0.1`. With the `days_ago` arg you can select how many days you want to show by default in your analytics graph. + ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -592,8 +632,10 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -## Migrating from an old repository (the original one): +## Migrating from an old repository (the original one) + If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` + ``` . +-- run.py @@ -602,18 +644,23 @@ If you already have a `twitch-cookies.pkl` and you don't want to log in again, p ``` ## Windows + 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)` + +- Stop use Windows :stuck_out_tongue_closed_eyes: +- Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` 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). ## Termux + Install the requirements + ``` pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib` LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow @@ -643,4 +690,5 @@ Now when we did everything we can run miner: `python run.py` Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) ## Disclaimer + This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index dbf9f639..f7dfd948 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -31,15 +31,15 @@ set_default_settings, ) -disableAnalytics = False +DISABLE_ANALYTICS = False try: - import enableAnalytics + import ENABLE_ANALYTICS except ImportError: - disableAnalytics = True + DISABLE_ANALYTICS = True # Analytics switch -if disableAnalytics is False: +if DISABLE_ANALYTICS is False: from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer # Suppress: @@ -90,7 +90,7 @@ def __init__( streamer_settings: StreamerSettings = StreamerSettings(), ): # Analytics switch - if disableAnalytics is False: + if DISABLE_ANALYTICS is False: Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) @@ -139,7 +139,7 @@ def __init__( signal.signal(sign, self.end) # Analytics switch - if disableAnalytics is False: + if DISABLE_ANALYTICS is False: def analytics( self, host: str = "127.0.0.1", diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 472677fe..fe140480 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -17,12 +17,12 @@ internet_connection_available, ) -disableAnalytics = False +DISABLE_ANALYTICS = False try: - import enableAnalytics + import ENABLE_ANALYTICS except ImportError: - disableAnalytics = True + DISABLE_ANALYTICS = True logger = logging.getLogger(__name__) @@ -186,7 +186,7 @@ def on_message(ws, message): balance = message.data["balance"]["balance"] ws.streamers[streamer_index].channel_points = balance # Analytics switch - if disableAnalytics is False: + if DISABLE_ANALYTICS is False: ws.streamers[streamer_index].persistent_series( event_type=message.data["point_gain"]["reason_code"] if message.type == "points-earned" @@ -208,7 +208,7 @@ def on_message(ws, message): reason_code, earned ) # Analytics switch - if disableAnalytics is False: + if DISABLE_ANALYTICS is False: ws.streamers[streamer_index].persistent_annotations( reason_code, f"+{earned} - {reason_code}" ) From 373ec84b908478e8e02d398af671fe6bfdd9e79a Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 12:57:20 +0300 Subject: [PATCH 040/140] batch file to remove __pycache__ --- DELETE_PYCACHE.bat | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 DELETE_PYCACHE.bat diff --git a/DELETE_PYCACHE.bat b/DELETE_PYCACHE.bat new file mode 100644 index 00000000..37901f48 --- /dev/null +++ b/DELETE_PYCACHE.bat @@ -0,0 +1,5 @@ +@echo off +rmdir /s /q __pycache__ +rmdir /s /q TwitchChannelPointsMiner\__pycache__ +rmdir /s /q TwitchChannelPointsMiner\classes\__pycache__ +rmdir /s /q TwitchChannelPointsMiner\classes\entities\__pycache__ \ No newline at end of file From 4eeaae3187ddc4b0940c9ade2bcea84654a33134 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:34:23 +0300 Subject: [PATCH 041/140] console_username in logger_settings --- TwitchChannelPointsMiner/logger.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/logger.py b/TwitchChannelPointsMiner/logger.py index 11ee5d2d..0faf7afb 100644 --- a/TwitchChannelPointsMiner/logger.py +++ b/TwitchChannelPointsMiner/logger.py @@ -62,6 +62,7 @@ class LoggerSettings: "save", "less", "console_level", + "console_username", "file_level", "emoji", "colored", @@ -76,6 +77,7 @@ def __init__( save: bool = True, less: bool = False, console_level: int = logging.INFO, + console_username: bool = False, file_level: int = logging.DEBUG, emoji: bool = platform.system() != "Windows", colored: bool = False, @@ -87,6 +89,7 @@ def __init__( self.save = save self.less = less self.console_level = console_level + self.console_username = console_username self.file_level = file_level self.emoji = emoji self.colored = colored @@ -169,14 +172,17 @@ def configure_loggers(username, settings): # Send log messages to another thread through the queue root_logger.addHandler(queue_handler) + # Adding a username to the format based on settings + console_username = "" if settings.console_username is False else f"[{username}] " + console_handler = logging.StreamHandler() console_handler.setLevel(settings.console_level) console_handler.setFormatter( GlobalFormatter( fmt=( - "%(asctime)s - %(levelname)s - [%(funcName)s]: %(message)s" + "%(asctime)s - %(levelname)s - [%(funcName)s]: " + console_username + "%(message)s" if settings.less is False - else "%(asctime)s - %(message)s" + else "%(asctime)s - " + console_username + "%(message)s" ), datefmt=( "%d/%m/%y %H:%M:%S" if settings.less is False else "%d/%m %H:%M:%S" From bf8d0dcbf1744dd03eb8aab37d57d556d022a626 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:45:25 +0300 Subject: [PATCH 042/140] console_username in logger_settings --- example.py | 1 + 1 file changed, 1 insertion(+) diff --git a/example.py b/example.py index 9e4393a8..4efe39c7 100644 --- a/example.py +++ b/example.py @@ -23,6 +23,7 @@ logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 From 06bd8ed85a171e9c89103c53a5adf3faed2208d4 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:54:14 +0300 Subject: [PATCH 043/140] console_username in logger_settings --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2ba087c7..93eb3ebb 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ twitch_miner = TwitchChannelPointsMiner( logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 @@ -414,6 +415,7 @@ You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCEN | `save` | bool | True | If you want to save logs in file (suggested) | | `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | | `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | +| `console_username` | bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602) | | `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | | `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | | `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | From 3a628194e58d6487100efe1e1e1305347c5485cc Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 21:31:04 +0300 Subject: [PATCH 044/140] Update README.md --- README.md | 255 ++++++++++++++++++++++-------------------------------- 1 file changed, 104 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 93eb3ebb..2fe27054 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,8 @@

**Credits** - -- Main idea: -- ~~Bet system (Selenium): ~~ +- Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner +- ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ > A simple script that will watch a stream for you and earn the channel points. @@ -20,14 +19,11 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-points-guide) ## 📢 Help wanted - Currently, we have a lot of PRs requests opened, but the time to test and improve It's less and less. If you want to help the community and the project, please test the following PRs and give us feedback: - - [Add SMART_HIGH_ODDS strategy #172](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/172) - [Add support for arbitrary filter functions #336](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/336) # README Contents - 1. 🤝 [Community](#community) 2. 🚀 [Main differences from the original repository](#main-differences-from-the-original-repository) 3. 🧾 [Logs feature](#logs-feature) @@ -52,8 +48,8 @@ Currently, we have a lot of PRs requests opened, but the time to test and improv 9. 📱 [Termux](#termux) 10. ⚠️ [Disclaimer](#disclaimer) -## Community +## Community If you have any type of issue, need help, or want to suggest a new feature, please open a GitHub Issue. Don't reach 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 with this project, please leave a star 🌟 and share it with your friends! 😎 @@ -70,7 +66,7 @@ If you want to offer me a coffee, I would be grateful ❤️ If you have any issues or you want to contribute, you are welcome! But please before read the [CONTRIBUTING.md](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/CONTRIBUTING.md) file. -## Main differences from the original repository +## Main differences from the original repository: - Improve the logging - Emoji, colors, file and soo on - Final report with all the data @@ -84,9 +80,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner - Join IRC Chat for increase watch-time and get StreamElements points [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) ## Logs feature - ### Full logs - ``` %d/%m/%y %H:%M:%S - INFO - [run]: 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m/%y %H:%M:%S - INFO - [run]: 🤓 Loading data for x streamers. Please wait ... @@ -117,9 +111,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - INFO - [update_raid]: 🎭 Joining raid from Streamer(username=streamer-username, channel_id=0000000, channel_points=64398) to another-username! %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +250 → Streamer(username=streamer-username, channel_id=0000000, channel_points=6845) - Reason: RAID. ``` - ### Less logs - ``` %d/%m %H:%M:%S - 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m %H:%M:%S - 🤓 Loading data for 13 streamers. Please wait ... @@ -148,9 +140,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m %H:%M:%S - 🎭 Joining raid from streamer-username (xxx points) to another-username! %d/%m %H:%M:%S - 🚀 +250 → streamer-username (xxx points) - Reason: RAID. ``` - -### Final report - +### Final report: ``` %d/%m/%y %H:%M:%S - 🛑 End session 'f738d438-cdbc-4cd5-90c4-1517576f1299' %d/%m/%y %H:%M:%S - 📄 Logs file: /.../path/Twitch-Channel-Points-Miner-v2/logs/username.timestamp.log @@ -158,23 +148,23 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - 📊 BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") - Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) - Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) - Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") - Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) - Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) - Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) - Result: {'type': 'WIN', 'won': 6531} + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") - Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) - Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) - Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 %d/%m/%y %H:%M:%S - 💰 CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) @@ -186,10 +176,8 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %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 - +## How to use: First of all please create a run.py file. You can just copy [example.py](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. - ```python # -*- coding: utf-8 -*- @@ -216,7 +204,7 @@ twitch_miner = TwitchChannelPointsMiner( logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info - console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 @@ -287,9 +275,7 @@ twitch_miner.mine( followers_order=FollowersOrder.ASC # Sort the followers list by follow date. ASC or DESC ) ``` - You can also use all the default values except for your username obv. Short version: - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.classes.Settings import FollowersOrder @@ -298,9 +284,7 @@ twitch_miner.mine(["streamer1", "streamer2"]) twitch_miner.mine(followers=True, followers_order=FollowersOrder.ASC) # Automatic use the followers list OR twitch_miner.mine(["streamer1", "streamer2"], followers=True, followers_order=FollowersOrder.DESC) # Mixed ``` - If you follow so many streamers on Twitch, but you don't want to mine points for all of them, you can blacklist the users with the `blacklist` keyword. [#94](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/94) - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -308,10 +292,8 @@ twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) # Blacklist exa ``` ### By cloning the repository - 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` . 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 @@ -322,9 +304,7 @@ pip install -r requirements.txt Start mining! `python run.py` 🥳 ### pip - Install the package via pip, you will find a stable version - maybe a different version from the master branch. - - `pip install Twitch-Channel-Points-Miner-v2` - Exceute the run.py file `python run.py` 🥳 @@ -362,7 +342,6 @@ services: ``` Example with docker run: - ```sh docker run \ -v $(pwd)/analytics:/usr/src/app/analytics \ @@ -376,7 +355,6 @@ docker run \ `$(pwd)` Could not work on Windows (cmd), please use the absolute path instead, like: `/path/of/your/cookies:/usr/src/app/cookies`. If you don't mount the volume for the analytics (or cookies or logs) folder, the folder will be automatically created on the Docker container, and you will lose all the data when it is stopped. If you don't have a cookie or It's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like - ```sh docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tkdalex/twitch-channel-points-miner-v2 ``` @@ -385,50 +363,44 @@ docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tk docker run --name user2-v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 tkdalex/twitch-channel-points-miner-v2 ``` -About the _Docker_ version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). +About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). ### Limits - > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` you can choose to download the followers sorted by follow date (ASC or DESC). ## Settings - Most of the settings are self-explained and are commented on in the example. You can watch only two streamers per time. With `priority` settings, you can select which streamers watch by use priority. You can use an array of priority or single item. I suggest using at least one priority from `ORDER`, `POINTS_ASCENDING`, `POINTS_DESCEDING` because, for example, If you set only `STREAK` after catch all watch streak, the script will stop to watch streamers. Available values are the following: - -- `STREAK` - Catch the watch streak from all streamers -- `DROPS` - Claim all drops from streamers with drops tags enabled -- `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) -- `ORDER` - Following the order of the list -- `POINTS_ASCENDING` - On top the streamers with the lowest points -- `POINTS_DESCEDING` - On top the streamers with the highest points + - `STREAK` - Catch the watch streak from all streamers + - `DROPS` - Claim all drops from streamers with drops tags enabled + - `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) + - `ORDER` - Following the order of the list + - `POINTS_ASCENDING` - On top the streamers with the lowest points + - `POINTS_DESCEDING` - On top the streamers with the highest points You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. ### LoggerSettings - -| Key | Type | Default | Description | -|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `save` | bool | True | If you want to save logs in file (suggested) | -| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | -| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `console_username` | bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602) | -| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | -| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | -| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | -| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | -| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | +| Key | Type | Default | Description | +|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `save` | bool | True | If you want to save logs in file (suggested) | +| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | +| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | +| `console_username`| bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602)| +| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | +| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | | `telegram` | Telegram | None | (Optional) Receive Telegram updates for multiple events list [#233](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/233) | | `discord` | Discord | None | (Optional) Receive Discord updates for multiple events list [#320](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/320) | #### Color Palette - Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. You can change the colors of all `Events` enum class. The colors allowed are all the Fore color from Colorama: `BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.` The script was developed to handle all the human error, lower-case upper case and more, but I want to suggest using the following code-style - ```python from colorama import Fore ColorPalette( @@ -448,18 +420,17 @@ ColorPalette( ``` #### Telegram - If you want to receive logs update on Telegram initiate a new Telegram class, else leave omit this parameter or set as None. - 1. Create a bot with [BotFather](https://t.me/botfather) 2. Get you `chat_id` with [GiveChatId](https://t.me/GiveChatId_Bot) -| Key | Type | Default | Description | -|----------------------- |----------------- |--------- |------------------------------------------------------------------- | -| `chat_id` | int | | Chat ID to send messages @GiveChatId | -| `token` | string | | Telegram API token @BotFather | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | -| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | +| Key | Type | Default | Description | +|----------------------- |----------------- |--------- |------------------------------------------------------------------- | +| `chat_id` | int | | Chat ID to send messages @GiveChatId | +| `token` | string | | Telegram API token @BotFather | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | +| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | + ```python Telegram( @@ -471,9 +442,7 @@ Telegram( ``` #### Discord - If you want to receive log updates on Discord initialize a new Discord class, else leave omit this parameter or set it as None [YT Video](https://www.youtube.com/watch?v=fKksxz2Gdnc) - 1. Go to the Server you want to receive updates 2. Click "Edit Channel" 3. Click "Integrations" @@ -482,10 +451,11 @@ If you want to receive log updates on Discord initialize a new Discord class, el 6. Name it if you want 7. Click on "Copy Webhook URL" -| Key | Type | Default | Description | -|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | -| `webhook_api` | string | | Discord webhook URL | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | + +| Key | Type | Default | Description | +|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | +| `webhook_api` | string | | Discord webhook URL | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | ```python Discord( @@ -494,54 +464,51 @@ Discord( ) ``` -#### Events -- `STREAMER_ONLINE` -- `STREAMER_OFFLINE` -- `GAIN_FOR_RAID` -- `GAIN_FOR_CLAIM` -- `GAIN_FOR_WATCH` -- `BET_WIN` -- `BET_LOSE` -- `BET_REFUND` -- `BET_FILTERS` -- `BET_GENERAL` -- `BET_FAILED` -- `BET_START` -- `BONUS_CLAIM` -- `JOIN_RAID` -- `DROP_CLAIM` -- `DROP_STATUS` +#### Events + - `STREAMER_ONLINE` + - `STREAMER_OFFLINE` + - `GAIN_FOR_RAID` + - `GAIN_FOR_CLAIM` + - `GAIN_FOR_WATCH` + - `BET_WIN` + - `BET_LOSE` + - `BET_REFUND` + - `BET_FILTERS` + - `BET_GENERAL` + - `BET_FAILED` + - `BET_START` + - `BONUS_CLAIM` + - `JOIN_RAID` + - `DROP_CLAIM` + - `DROP_STATUS` ### StreamerSettings - -| Key | Type | Default | Description | -|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | -| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | -| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | -| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | -| `bet` | BetSettings | | Rules to follow for the bet | -| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | +| Key | Type | Default | Description | +|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `bet` | BetSettings | | Rules to follow for the bet | +| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | Allowed values for `chat` are: - - `ALWAYS` Join in IRC chat and never leave - `NEVER` Never join IRC chat - `ONLINE` Partecipate to IRC chat if the streamer is online (leave if offline) - `OFFLINE` Partecipate to IRC chat if the streamer is offline (leave if online) ### BetSettings - -| Key | Type | Default | Description | -|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `strategy` | Strategy | SMART | Choose your strategy! See 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) | -| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | -| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | +| 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) | +| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | +| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | #### Bet strategy @@ -561,17 +528,14 @@ Here a concrete example: - **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' - ### FilterCondition - -| Key | Type | Default | Description | -|------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `by` | OutcomeKeys | None | Key to apply the filter | -| `where` | Condition | None | Condition that should match for place bet | -| `value` | number | None | Value to compare | +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | Allowed values for `by` are: - - `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] - `ODDS_PERCENTAGE` (no sum) [Doesn't make sense to sum odds] - `ODDS` (no sum) [Doesn't make sense to sum odds] @@ -584,7 +548,6 @@ 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 is greater than 200 `FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.GT, value=200)` - If you want to place the bet ONLY if the winning odd of your decision is greater than or equal to 1.3 @@ -605,28 +568,17 @@ Here's a concrete example. Let's suppose we have a bet that is opened with a tim - **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened) ## Analytics - We have recently introduced a little frontend where you can show with a chart you points trend. The script will spawn a Flask web-server on your machine where you can select binding address and port. The chart provides some annotation to handle the prediction and watch strike events. Usually annotation are used to notice big increase / decrease of points. If you want to can disable annotations. On each (x, y) points Its present a tooltip that show points, date time and reason of points gained / lost. This web page was just a funny idea, and it is not intended to use for a professional usage. If you want you can toggle the dark theme with the dedicated checkbox. -### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics - -Disabling Analytics significantly reduces memory consumption and saves some disk space. - -- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. -- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. - -This file can be empty. - | Light theme | Dark theme | | ----------- | ---------- | | ![Light theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-light.png) | ![Dark theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-dark.png) | For use this feature just call the `analytics` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) The chart will be autofreshed each `refresh` minutes. If you want to connect from one to second machine that have that webpanel you have to use `0.0.0.0` instead of `127.0.0.1`. With the `days_ago` arg you can select how many days you want to show by default in your analytics graph. - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -634,10 +586,17 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -## Migrating from an old repository (the original one) +### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics -If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` +Disabling Analytics significantly reduces memory consumption and saves some disk space. +- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. +- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. + +This file can be empty. + +## Migrating from an old repository (the original one): +If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` ``` . +-- run.py @@ -646,23 +605,18 @@ If you already have a `twitch-cookies.pkl` and you don't want to log in again, p ``` ## Windows - 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)` + - Stop use Windows :stuck_out_tongue_closed_eyes: + - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` 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). ## Termux - Install the requirements - ``` pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib` LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow @@ -692,5 +646,4 @@ Now when we did everything we can run miner: `python run.py` Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) ## Disclaimer - This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. From 5bce7d06bc66a4ad114525c6f5ab6c259ca7113e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 21:31:39 +0300 Subject: [PATCH 045/140] Update README.md --- README.md | 255 ++++++++++++++++++++++-------------------------------- 1 file changed, 104 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 93eb3ebb..2fe27054 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,8 @@

**Credits** - -- Main idea: -- ~~Bet system (Selenium): ~~ +- Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner +- ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ > A simple script that will watch a stream for you and earn the channel points. @@ -20,14 +19,11 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-points-guide) ## 📢 Help wanted - Currently, we have a lot of PRs requests opened, but the time to test and improve It's less and less. If you want to help the community and the project, please test the following PRs and give us feedback: - - [Add SMART_HIGH_ODDS strategy #172](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/172) - [Add support for arbitrary filter functions #336](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/336) # README Contents - 1. 🤝 [Community](#community) 2. 🚀 [Main differences from the original repository](#main-differences-from-the-original-repository) 3. 🧾 [Logs feature](#logs-feature) @@ -52,8 +48,8 @@ Currently, we have a lot of PRs requests opened, but the time to test and improv 9. 📱 [Termux](#termux) 10. ⚠️ [Disclaimer](#disclaimer) -## Community +## Community If you have any type of issue, need help, or want to suggest a new feature, please open a GitHub Issue. Don't reach 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 with this project, please leave a star 🌟 and share it with your friends! 😎 @@ -70,7 +66,7 @@ If you want to offer me a coffee, I would be grateful ❤️ If you have any issues or you want to contribute, you are welcome! But please before read the [CONTRIBUTING.md](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/CONTRIBUTING.md) file. -## Main differences from the original repository +## Main differences from the original repository: - Improve the logging - Emoji, colors, file and soo on - Final report with all the data @@ -84,9 +80,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner - Join IRC Chat for increase watch-time and get StreamElements points [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) ## Logs feature - ### Full logs - ``` %d/%m/%y %H:%M:%S - INFO - [run]: 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m/%y %H:%M:%S - INFO - [run]: 🤓 Loading data for x streamers. Please wait ... @@ -117,9 +111,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - INFO - [update_raid]: 🎭 Joining raid from Streamer(username=streamer-username, channel_id=0000000, channel_points=64398) to another-username! %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +250 → Streamer(username=streamer-username, channel_id=0000000, channel_points=6845) - Reason: RAID. ``` - ### Less logs - ``` %d/%m %H:%M:%S - 💣 Start session: '9eb934b0-1684-4a62-b3e2-ba097bd67d35' %d/%m %H:%M:%S - 🤓 Loading data for 13 streamers. Please wait ... @@ -148,9 +140,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m %H:%M:%S - 🎭 Joining raid from streamer-username (xxx points) to another-username! %d/%m %H:%M:%S - 🚀 +250 → streamer-username (xxx points) - Reason: RAID. ``` - -### Final report - +### Final report: ``` %d/%m/%y %H:%M:%S - 🛑 End session 'f738d438-cdbc-4cd5-90c4-1517576f1299' %d/%m/%y %H:%M:%S - 📄 Logs file: /.../path/Twitch-Channel-Points-Miner-v2/logs/username.timestamp.log @@ -158,23 +148,23 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %d/%m/%y %H:%M:%S - 📊 BetSettings(Strategy=Strategy.SMART, Percentage=7, PercentageGap=20, MaxPoints=7500 %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title="Event Title1") - Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) - Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) - Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=67247) + Bet(TotalUsers=1k, TotalPoints=11M), Decision={'choice': 'B', 'amount': 5289, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 7M, Users: 641 (58.49%), Odds: 1.6, (5}%) + Outcome1(NO (PINK),Points: 4M, Users: 455 (41.51%), Odds: 2.65 (37.74%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=yyyy-yyyy-yyyy-yyyy, title="Event Title2") - Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) - Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) - Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) - Result: {'type': 'WIN', 'won': 6531} + Streamer(username=streamer-username, channel_id=0000000, channel_points=3453464) + Bet(TotalUsers=921, TotalPoints=11M), Decision={'choice': 'A', 'amount': 4926, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 9M, Users: 562 (61.02%), Odds: 1.31 (76.34%)) + Outcome1(YES (PINK) Points: 3M, Users: 359 (38.98%), Odds: 4.21 (23.75%)) + Result: {'type': 'WIN', 'won': 6531} %d/%m/%y %H:%M:%S - 📊 EventPrediction(event_id=ad152117-251b-4666-b683-18e5390e56c3, title="Event Title3") - Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) - Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) - Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) - Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) - Result: {'type': 'LOSE', 'won': 0} + Streamer(username=streamer-username, channel_id=0000000, channel_points=45645645) + Bet(TotalUsers=260, TotalPoints=3M), Decision={'choice': 'A', 'amount': 5054, 'id': 'xxxx-yyyy-zzzz'}) + Outcome0(YES (BLUE) Points: 689k, Users: 114 (43.85%), Odds: 4.24 (23.58%)) + Outcome1(NO (PINK) Points: 2M, Users: 146 (56.15%), Odds: 1.31 (76.34%)) + Result: {'type': 'LOSE', 'won': 0} %d/%m/%y %H:%M:%S - 🤖 Streamer(username=streamer-username, channel_id=0000000, channel_points=67247), Total points gained (after farming - before farming): -7838 %d/%m/%y %H:%M:%S - 💰 CLAIM(11 times, 550 gained), PREDICTION(1 times, 6531 gained), WATCH(35 times, 350 gained) @@ -186,10 +176,8 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner %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 - +## How to use: First of all please create a run.py file. You can just copy [example.py](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. - ```python # -*- coding: utf-8 -*- @@ -216,7 +204,7 @@ twitch_miner = TwitchChannelPointsMiner( logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info - console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 @@ -287,9 +275,7 @@ twitch_miner.mine( followers_order=FollowersOrder.ASC # Sort the followers list by follow date. ASC or DESC ) ``` - You can also use all the default values except for your username obv. Short version: - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.classes.Settings import FollowersOrder @@ -298,9 +284,7 @@ twitch_miner.mine(["streamer1", "streamer2"]) twitch_miner.mine(followers=True, followers_order=FollowersOrder.ASC) # Automatic use the followers list OR twitch_miner.mine(["streamer1", "streamer2"], followers=True, followers_order=FollowersOrder.DESC) # Mixed ``` - If you follow so many streamers on Twitch, but you don't want to mine points for all of them, you can blacklist the users with the `blacklist` keyword. [#94](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/94) - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -308,10 +292,8 @@ twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) # Blacklist exa ``` ### By cloning the repository - 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` . 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 @@ -322,9 +304,7 @@ pip install -r requirements.txt Start mining! `python run.py` 🥳 ### pip - Install the package via pip, you will find a stable version - maybe a different version from the master branch. - - `pip install Twitch-Channel-Points-Miner-v2` - Exceute the run.py file `python run.py` 🥳 @@ -362,7 +342,6 @@ services: ``` Example with docker run: - ```sh docker run \ -v $(pwd)/analytics:/usr/src/app/analytics \ @@ -376,7 +355,6 @@ docker run \ `$(pwd)` Could not work on Windows (cmd), please use the absolute path instead, like: `/path/of/your/cookies:/usr/src/app/cookies`. If you don't mount the volume for the analytics (or cookies or logs) folder, the folder will be automatically created on the Docker container, and you will lose all the data when it is stopped. If you don't have a cookie or It's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like - ```sh docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tkdalex/twitch-channel-points-miner-v2 ``` @@ -385,50 +363,44 @@ docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tk docker run --name user2-v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 tkdalex/twitch-channel-points-miner-v2 ``` -About the _Docker_ version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). +About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). ### Limits - > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` you can choose to download the followers sorted by follow date (ASC or DESC). ## Settings - Most of the settings are self-explained and are commented on in the example. You can watch only two streamers per time. With `priority` settings, you can select which streamers watch by use priority. You can use an array of priority or single item. I suggest using at least one priority from `ORDER`, `POINTS_ASCENDING`, `POINTS_DESCEDING` because, for example, If you set only `STREAK` after catch all watch streak, the script will stop to watch streamers. Available values are the following: - -- `STREAK` - Catch the watch streak from all streamers -- `DROPS` - Claim all drops from streamers with drops tags enabled -- `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) -- `ORDER` - Following the order of the list -- `POINTS_ASCENDING` - On top the streamers with the lowest points -- `POINTS_DESCEDING` - On top the streamers with the highest points + - `STREAK` - Catch the watch streak from all streamers + - `DROPS` - Claim all drops from streamers with drops tags enabled + - `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first) + - `ORDER` - Following the order of the list + - `POINTS_ASCENDING` - On top the streamers with the lowest points + - `POINTS_DESCEDING` - On top the streamers with the highest points You can combine all priority but keep in mind that use `ORDER` and `POINTS_ASCENDING` in the same settings doesn't make sense. ### LoggerSettings - -| Key | Type | Default | Description | -|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `save` | bool | True | If you want to save logs in file (suggested) | -| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | -| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | -| `console_username` | bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602) | -| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | -| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | -| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | -| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | -| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | +| Key | Type | Default | Description | +|----------------- |----------------- |-------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `save` | bool | True | If you want to save logs in file (suggested) | +| `less` | bool | False | Reduce the logging format and message verbosity [#10](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/10) | +| `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | +| `console_username`| bool | False | Adds a username to every log line in the console if True. [#602](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/602)| +| `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big, use logging.INFO | +| `emoji` | bool | For Windows is False else True | On Windows, we have a problem printing emoji. Set to false if you have a problem | +| `colored` | bool | True | If you want to print colored text [#45](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/45) [#82](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/82) | +| `auto_clear` | bool | True | Create a file rotation handler with interval = 1D and backupCount = 7 [#215](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/215) | +| `color_palette` | ColorPalette | All messages are Fore.RESET except WIN and LOSE bet (GREEN and RED) | Create your custom color palette. Read more above. | | `telegram` | Telegram | None | (Optional) Receive Telegram updates for multiple events list [#233](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/233) | | `discord` | Discord | None | (Optional) Receive Discord updates for multiple events list [#320](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/320) | #### Color Palette - Now you can customize the color of the terminal message. We have created a default ColorPalette that provide all the message with `DEFAULT (RESET)` color and the `BET_WIN` and `BET_LOSE` message `GREEN` and `RED` respectively. You can change the colors of all `Events` enum class. The colors allowed are all the Fore color from Colorama: `BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.` The script was developed to handle all the human error, lower-case upper case and more, but I want to suggest using the following code-style - ```python from colorama import Fore ColorPalette( @@ -448,18 +420,17 @@ ColorPalette( ``` #### Telegram - If you want to receive logs update on Telegram initiate a new Telegram class, else leave omit this parameter or set as None. - 1. Create a bot with [BotFather](https://t.me/botfather) 2. Get you `chat_id` with [GiveChatId](https://t.me/GiveChatId_Bot) -| Key | Type | Default | Description | -|----------------------- |----------------- |--------- |------------------------------------------------------------------- | -| `chat_id` | int | | Chat ID to send messages @GiveChatId | -| `token` | string | | Telegram API token @BotFather | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | -| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | +| Key | Type | Default | Description | +|----------------------- |----------------- |--------- |------------------------------------------------------------------- | +| `chat_id` | int | | Chat ID to send messages @GiveChatId | +| `token` | string | | Telegram API token @BotFather | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | +| `disable_notification` | bool | false | Revoke the notification (sound/vibration) | + ```python Telegram( @@ -471,9 +442,7 @@ Telegram( ``` #### Discord - If you want to receive log updates on Discord initialize a new Discord class, else leave omit this parameter or set it as None [YT Video](https://www.youtube.com/watch?v=fKksxz2Gdnc) - 1. Go to the Server you want to receive updates 2. Click "Edit Channel" 3. Click "Integrations" @@ -482,10 +451,11 @@ If you want to receive log updates on Discord initialize a new Discord class, el 6. Name it if you want 7. Click on "Copy Webhook URL" -| Key | Type | Default | Description | -|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | -| `webhook_api` | string | | Discord webhook URL | -| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | + +| Key | Type | Default | Description | +|----------------------- |--------------------- |-------------- |------------------------------------------------------------------- | +| `webhook_api` | string | | Discord webhook URL | +| `events` | list | | Only these events will be sent to the chat. Array of Event. or str | ```python Discord( @@ -494,54 +464,51 @@ Discord( ) ``` -#### Events -- `STREAMER_ONLINE` -- `STREAMER_OFFLINE` -- `GAIN_FOR_RAID` -- `GAIN_FOR_CLAIM` -- `GAIN_FOR_WATCH` -- `BET_WIN` -- `BET_LOSE` -- `BET_REFUND` -- `BET_FILTERS` -- `BET_GENERAL` -- `BET_FAILED` -- `BET_START` -- `BONUS_CLAIM` -- `JOIN_RAID` -- `DROP_CLAIM` -- `DROP_STATUS` +#### Events + - `STREAMER_ONLINE` + - `STREAMER_OFFLINE` + - `GAIN_FOR_RAID` + - `GAIN_FOR_CLAIM` + - `GAIN_FOR_WATCH` + - `BET_WIN` + - `BET_LOSE` + - `BET_REFUND` + - `BET_FILTERS` + - `BET_GENERAL` + - `BET_FAILED` + - `BET_START` + - `BONUS_CLAIM` + - `JOIN_RAID` + - `DROP_CLAIM` + - `DROP_STATUS` ### StreamerSettings - -| Key | Type | Default | Description | -|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | -| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | -| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | -| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | -| `bet` | BetSettings | | Rules to follow for the bet | -| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | +| Key | Type | Default | Description | +|-------------------- |------------- |-------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `make_predictions` | bool | True | Choose if you want to make predictions / bet or not | +| `follow_raid` | bool | True | Choose if you want to follow raid +250 points | +| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) | +| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) | +| `bet` | BetSettings | | Rules to follow for the bet | +| `chat` | ChatPresence | ONLINE | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) | Allowed values for `chat` are: - - `ALWAYS` Join in IRC chat and never leave - `NEVER` Never join IRC chat - `ONLINE` Partecipate to IRC chat if the streamer is online (leave if offline) - `OFFLINE` Partecipate to IRC chat if the streamer is offline (leave if online) ### BetSettings - -| Key | Type | Default | Description | -|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `strategy` | Strategy | SMART | Choose your strategy! See 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) | -| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | -| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | +| 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) | +| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet | +| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value | #### Bet strategy @@ -561,17 +528,14 @@ Here a concrete example: - **SMART**: Calculate the percentage based on the users. The percentages are: 'over 7.5': 70% and 'under 7.5': 30%. If the difference between the two percentages is higher than `percentage_gap` select the highest percentage, else the highest odds. In this case if percentage_gap = 20 ; 70-30 = 40 > percentage_gap, so the bot will select 'over 7.5' - ### FilterCondition - -| Key | Type | Default | Description | -|------------- |------------- |--------- |---------------------------------------------------------------------------------- | -| `by` | OutcomeKeys | None | Key to apply the filter | -| `where` | Condition | None | Condition that should match for place bet | -| `value` | number | None | Value to compare | +| Key | Type | Default | Description | +|------------- |------------- |--------- |---------------------------------------------------------------------------------- | +| `by` | OutcomeKeys | None | Key to apply the filter | +| `where` | Condition | None | Condition that should match for place bet | +| `value` | number | None | Value to compare | Allowed values for `by` are: - - `PERCENTAGE_USERS` (no sum) [Would never want a sum as it'd always be 100%] - `ODDS_PERCENTAGE` (no sum) [Doesn't make sense to sum odds] - `ODDS` (no sum) [Doesn't make sense to sum odds] @@ -584,7 +548,6 @@ 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 is greater than 200 `FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.GT, value=200)` - If you want to place the bet ONLY if the winning odd of your decision is greater than or equal to 1.3 @@ -605,28 +568,17 @@ Here's a concrete example. Let's suppose we have a bet that is opened with a tim - **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened) ## Analytics - We have recently introduced a little frontend where you can show with a chart you points trend. The script will spawn a Flask web-server on your machine where you can select binding address and port. The chart provides some annotation to handle the prediction and watch strike events. Usually annotation are used to notice big increase / decrease of points. If you want to can disable annotations. On each (x, y) points Its present a tooltip that show points, date time and reason of points gained / lost. This web page was just a funny idea, and it is not intended to use for a professional usage. If you want you can toggle the dark theme with the dedicated checkbox. -### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics - -Disabling Analytics significantly reduces memory consumption and saves some disk space. - -- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. -- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. - -This file can be empty. - | Light theme | Dark theme | | ----------- | ---------- | | ![Light theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-light.png) | ![Dark theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-dark.png) | For use this feature just call the `analytics` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) The chart will be autofreshed each `refresh` minutes. If you want to connect from one to second machine that have that webpanel you have to use `0.0.0.0` instead of `127.0.0.1`. With the `days_ago` arg you can select how many days you want to show by default in your analytics graph. - ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner twitch_miner = TwitchChannelPointsMiner("your-twitch-username") @@ -634,10 +586,17 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -## Migrating from an old repository (the original one) +### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics -If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` +Disabling Analytics significantly reduces memory consumption and saves some disk space. +- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. +- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. + +This file can be empty. + +## Migrating from an old repository (the original one): +If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` ``` . +-- run.py @@ -646,23 +605,18 @@ If you already have a `twitch-cookies.pkl` and you don't want to log in again, p ``` ## Windows - 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)` + - Stop use Windows :stuck_out_tongue_closed_eyes: + - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` 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). ## Termux - Install the requirements - ``` pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib` LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow @@ -692,5 +646,4 @@ Now when we did everything we can run miner: `python run.py` Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) ## Disclaimer - This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. From 8be4540d608512e3a46fdf8002ca4b82e7d96030 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 21:43:34 +0300 Subject: [PATCH 046/140] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fe27054..77b4c3c4 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ twitch_miner = TwitchChannelPointsMiner( logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info - console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 From b50be92ba335cd7dcc545c233dd677ca92704b70 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 21 Oct 2022 21:44:26 +0300 Subject: [PATCH 047/140] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fe27054..77b4c3c4 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ twitch_miner = TwitchChannelPointsMiner( logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info - console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts + console_username=False, # Adds a username to every console log line if True. Useful when you have many open consoles with different accounts 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 From f00cc22713293adcff5bc5c3c29460e5bbbae240 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:50:27 +0300 Subject: [PATCH 048/140] analytics as an option in twitch_miner --- ENABLE_ANALYTICS.py | 4 ---- .../TwitchChannelPointsMiner.py | 20 +++++++------------ TwitchChannelPointsMiner/classes/Settings.py | 2 +- .../classes/WebSocketsPool.py | 14 +++---------- example.py | 1 + 5 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 ENABLE_ANALYTICS.py diff --git a/ENABLE_ANALYTICS.py b/ENABLE_ANALYTICS.py deleted file mode 100644 index 9ec9eca0..00000000 --- a/ENABLE_ANALYTICS.py +++ /dev/null @@ -1,4 +0,0 @@ -# Significantly reduces memory consumption and saves some disk space. -# Remove this file if you don't need Analytics. Or rename it to something different, like "DISABLE_ANALYTICS.py". -# To enable Analytics back - just create this file again. Or rename it back to "ENABLE_ANALYTICS.py". -# This file can be empty. \ No newline at end of file diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index f7dfd948..f0004318 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -31,17 +31,6 @@ set_default_settings, ) -DISABLE_ANALYTICS = False - -try: - import ENABLE_ANALYTICS -except ImportError: - DISABLE_ANALYTICS = True - -# Analytics switch -if DISABLE_ANALYTICS is False: - from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer - # Suppress: # - chardet.charsetprober - [feed] # - chardet.charsetprober - [get_confidence] @@ -63,6 +52,7 @@ class TwitchChannelPointsMiner: "username", "twitch", "claim_drops_startup", + "analytics", "priority", "streamers", "events_predictions", @@ -82,6 +72,7 @@ def __init__( username: str, password: str = None, claim_drops_startup: bool = False, + analytics: bool = False, # Settings for logging and selenium as you can see. priority: list = [Priority.STREAK, Priority.DROPS, Priority.ORDER], # This settings will be global shared trought Settings class @@ -90,7 +81,10 @@ def __init__( streamer_settings: StreamerSettings = StreamerSettings(), ): # Analytics switch - if DISABLE_ANALYTICS is False: + Settings.analytics = analytics + + if Settings.analytics is True: + from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) @@ -139,7 +133,7 @@ def __init__( signal.signal(sign, self.end) # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: def analytics( self, host: str = "127.0.0.1", diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index 80624083..1fdf1f88 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -20,7 +20,7 @@ def __str__(self): # Empty object shared between class class Settings(object): - __slots__ = ["logger", "streamer_settings"] + __slots__ = ["logger", "streamer_settings", "analytics"] class Events(Enum): diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index fe140480..cb565b23 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -9,7 +9,7 @@ from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction from TwitchChannelPointsMiner.classes.entities.Message import Message from TwitchChannelPointsMiner.classes.entities.Raid import Raid -from TwitchChannelPointsMiner.classes.Settings import Events +from TwitchChannelPointsMiner.classes.Settings import Events, Settings from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import ( @@ -17,16 +17,8 @@ internet_connection_available, ) -DISABLE_ANALYTICS = False - -try: - import ENABLE_ANALYTICS -except ImportError: - DISABLE_ANALYTICS = True - logger = logging.getLogger(__name__) - class WebSocketsPool: __slots__ = ["ws", "twitch", "streamers", "events_predictions"] @@ -186,7 +178,7 @@ def on_message(ws, message): balance = message.data["balance"]["balance"] ws.streamers[streamer_index].channel_points = balance # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: ws.streamers[streamer_index].persistent_series( event_type=message.data["point_gain"]["reason_code"] if message.type == "points-earned" @@ -208,7 +200,7 @@ def on_message(ws, message): reason_code, earned ) # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: ws.streamers[streamer_index].persistent_annotations( reason_code, f"+{earned} - {reason_code}" ) diff --git a/example.py b/example.py index 4efe39c7..aa16bc20 100644 --- a/example.py +++ b/example.py @@ -20,6 +20,7 @@ Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info From da8215bb1d3c6d79a7007168ed19f6d0e1cad175 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:52:20 +0300 Subject: [PATCH 049/140] Update example.py --- example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.py b/example.py index aa16bc20..d0cb700b 100644 --- a/example.py +++ b/example.py @@ -20,7 +20,7 @@ Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], - analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info From a411ed35bc5250581e4b6508e59d78e78032fca9 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:03:55 +0300 Subject: [PATCH 050/140] Update README.md --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 77b4c3c4..b59b7395 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ twitch_miner = TwitchChannelPointsMiner( Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info @@ -586,14 +587,11 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics +### `analytics` option in `twitch_minerfile` toggles Analytics -Disabling Analytics significantly reduces memory consumption and saves some disk space. +Disabling Analytics significantly reduces memory consumption and saves some disk space by not creating and writing `/analytics/*.json`. -- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. -- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. - -This file can be empty. +Set this option to `True` if you need Analytics. Otherwise set this option to `False` (default value). ## Migrating from an old repository (the original one): If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` From 23c556392a1f2e75b687eb178f66920cc7d55ff3 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:50:27 +0300 Subject: [PATCH 051/140] analytics as an option in twitch_miner --- ENABLE_ANALYTICS.py | 4 ---- .../TwitchChannelPointsMiner.py | 20 +++++++------------ TwitchChannelPointsMiner/classes/Settings.py | 2 +- .../classes/WebSocketsPool.py | 14 +++---------- example.py | 1 + 5 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 ENABLE_ANALYTICS.py diff --git a/ENABLE_ANALYTICS.py b/ENABLE_ANALYTICS.py deleted file mode 100644 index 9ec9eca0..00000000 --- a/ENABLE_ANALYTICS.py +++ /dev/null @@ -1,4 +0,0 @@ -# Significantly reduces memory consumption and saves some disk space. -# Remove this file if you don't need Analytics. Or rename it to something different, like "DISABLE_ANALYTICS.py". -# To enable Analytics back - just create this file again. Or rename it back to "ENABLE_ANALYTICS.py". -# This file can be empty. \ No newline at end of file diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index f7dfd948..f0004318 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -31,17 +31,6 @@ set_default_settings, ) -DISABLE_ANALYTICS = False - -try: - import ENABLE_ANALYTICS -except ImportError: - DISABLE_ANALYTICS = True - -# Analytics switch -if DISABLE_ANALYTICS is False: - from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer - # Suppress: # - chardet.charsetprober - [feed] # - chardet.charsetprober - [get_confidence] @@ -63,6 +52,7 @@ class TwitchChannelPointsMiner: "username", "twitch", "claim_drops_startup", + "analytics", "priority", "streamers", "events_predictions", @@ -82,6 +72,7 @@ def __init__( username: str, password: str = None, claim_drops_startup: bool = False, + analytics: bool = False, # Settings for logging and selenium as you can see. priority: list = [Priority.STREAK, Priority.DROPS, Priority.ORDER], # This settings will be global shared trought Settings class @@ -90,7 +81,10 @@ def __init__( streamer_settings: StreamerSettings = StreamerSettings(), ): # Analytics switch - if DISABLE_ANALYTICS is False: + Settings.analytics = analytics + + if Settings.analytics is True: + from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) @@ -139,7 +133,7 @@ def __init__( signal.signal(sign, self.end) # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: def analytics( self, host: str = "127.0.0.1", diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index 80624083..1fdf1f88 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -20,7 +20,7 @@ def __str__(self): # Empty object shared between class class Settings(object): - __slots__ = ["logger", "streamer_settings"] + __slots__ = ["logger", "streamer_settings", "analytics"] class Events(Enum): diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index fe140480..cb565b23 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -9,7 +9,7 @@ from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction from TwitchChannelPointsMiner.classes.entities.Message import Message from TwitchChannelPointsMiner.classes.entities.Raid import Raid -from TwitchChannelPointsMiner.classes.Settings import Events +from TwitchChannelPointsMiner.classes.Settings import Events, Settings from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.constants import WEBSOCKET from TwitchChannelPointsMiner.utils import ( @@ -17,16 +17,8 @@ internet_connection_available, ) -DISABLE_ANALYTICS = False - -try: - import ENABLE_ANALYTICS -except ImportError: - DISABLE_ANALYTICS = True - logger = logging.getLogger(__name__) - class WebSocketsPool: __slots__ = ["ws", "twitch", "streamers", "events_predictions"] @@ -186,7 +178,7 @@ def on_message(ws, message): balance = message.data["balance"]["balance"] ws.streamers[streamer_index].channel_points = balance # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: ws.streamers[streamer_index].persistent_series( event_type=message.data["point_gain"]["reason_code"] if message.type == "points-earned" @@ -208,7 +200,7 @@ def on_message(ws, message): reason_code, earned ) # Analytics switch - if DISABLE_ANALYTICS is False: + if Settings.analytics is True: ws.streamers[streamer_index].persistent_annotations( reason_code, f"+{earned} - {reason_code}" ) diff --git a/example.py b/example.py index 4efe39c7..aa16bc20 100644 --- a/example.py +++ b/example.py @@ -20,6 +20,7 @@ Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info From 708176e16e6187cd6bb6a40efd62455d83b3ebbe Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:52:20 +0300 Subject: [PATCH 052/140] Update example.py --- example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.py b/example.py index aa16bc20..d0cb700b 100644 --- a/example.py +++ b/example.py @@ -20,7 +20,7 @@ Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], - analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info From d79eea3ebd9908145b6a13d54730a6e204a8288e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:03:55 +0300 Subject: [PATCH 053/140] Update README.md --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 77b4c3c4..b59b7395 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ twitch_miner = TwitchChannelPointsMiner( Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], + analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info @@ -586,14 +587,11 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -### `ENABLE_ANALYTICS.py` file in the main directory toggles Analytics +### `analytics` option in `twitch_minerfile` toggles Analytics -Disabling Analytics significantly reduces memory consumption and saves some disk space. +Disabling Analytics significantly reduces memory consumption and saves some disk space by not creating and writing `/analytics/*.json`. -- Remove this file if you don't need Analytics. Or rename it to something different, like `DISABLE_ANALYTICS.py`. -- To enable Analytics back - just create this file again. Or rename it back to `ENABLE_ANALYTICS.py`. - -This file can be empty. +Set this option to `True` if you need Analytics. Otherwise set this option to `False` (default value). ## Migrating from an old repository (the original one): If you already have a `twitch-cookies.pkl` and you don't want to log in again, please create a `cookies/` folder in the current directory and then copy the .pkl file with a new name `your-twitch-username.pkl` From 7e09c1748142fa3684f9059ccf6a5d122d93a142 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:58:27 +0300 Subject: [PATCH 054/140] formatting --- 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 cb565b23..9e28e4c5 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -169,7 +169,7 @@ def on_message(ws, message): ws.last_message_timestamp = message.timestamp ws.last_message_type_channel = message.identifier - + streamer_index = get_streamer_index(ws.streamers, message.channel_id) if streamer_index != -1: try: From 2ffe7a4a1df48f8f953d5d9ba2a8b724759ea54a Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:44:45 +0300 Subject: [PATCH 055/140] analytics fix --- .../TwitchChannelPointsMiner.py | 31 ++++++++++--------- TwitchChannelPointsMiner/classes/Settings.py | 2 +- .../classes/WebSocketsPool.py | 4 +-- example.py | 4 ++- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index f0004318..78e3c601 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -52,7 +52,7 @@ class TwitchChannelPointsMiner: "username", "twitch", "claim_drops_startup", - "analytics", + "enable_analytics", "priority", "streamers", "events_predictions", @@ -72,7 +72,7 @@ def __init__( username: str, password: str = None, claim_drops_startup: bool = False, - analytics: bool = False, + enable_analytics: bool = False, # Settings for logging and selenium as you can see. priority: list = [Priority.STREAK, Priority.DROPS, Priority.ORDER], # This settings will be global shared trought Settings class @@ -81,10 +81,9 @@ def __init__( streamer_settings: StreamerSettings = StreamerSettings(), ): # Analytics switch - Settings.analytics = analytics + Settings.enable_analytics = enable_analytics - if Settings.analytics is True: - from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer + if enable_analytics is True: Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) @@ -132,21 +131,25 @@ def __init__( for sign in [signal.SIGINT, signal.SIGSEGV, signal.SIGTERM]: signal.signal(sign, self.end) - # Analytics switch - if Settings.analytics is True: - def analytics( - self, - host: str = "127.0.0.1", - port: int = 5000, - refresh: int = 5, - days_ago: int = 7, - ): + def analytics( + self, + host: str = "127.0.0.1", + port: int = 5000, + refresh: int = 5, + days_ago: int = 7, + ): + # Analytics switch + if Settings.enable_analytics is True: + from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer + http_server = AnalyticsServer( host=host, port=port, refresh=refresh, days_ago=days_ago ) http_server.daemon = True http_server.name = "Analytics Thread" http_server.start() + else: + logger.error("Can't start analytics(), please set enable_analytics=True") def mine( self, diff --git a/TwitchChannelPointsMiner/classes/Settings.py b/TwitchChannelPointsMiner/classes/Settings.py index 1fdf1f88..e8c8b4f1 100644 --- a/TwitchChannelPointsMiner/classes/Settings.py +++ b/TwitchChannelPointsMiner/classes/Settings.py @@ -20,7 +20,7 @@ def __str__(self): # Empty object shared between class class Settings(object): - __slots__ = ["logger", "streamer_settings", "analytics"] + __slots__ = ["logger", "streamer_settings", "enable_analytics"] class Events(Enum): diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 9e28e4c5..72d24a06 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -178,7 +178,7 @@ def on_message(ws, message): balance = message.data["balance"]["balance"] ws.streamers[streamer_index].channel_points = balance # Analytics switch - if Settings.analytics is True: + if Settings.enable_analytics is True: ws.streamers[streamer_index].persistent_series( event_type=message.data["point_gain"]["reason_code"] if message.type == "points-earned" @@ -200,7 +200,7 @@ def on_message(ws, message): reason_code, earned ) # Analytics switch - if Settings.analytics is True: + if Settings.enable_analytics is True: ws.streamers[streamer_index].persistent_annotations( reason_code, f"+{earned} - {reason_code}" ) diff --git a/example.py b/example.py index d0cb700b..32ea33df 100644 --- a/example.py +++ b/example.py @@ -20,7 +20,7 @@ Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], - analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + enable_analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info @@ -77,6 +77,8 @@ # For example, if in the mine function you don't provide any value for 'make_prediction' but you have set it on TwitchChannelPointsMiner instance, the script will take the value from here. # If you haven't set any value even in the instance the default one will be used +#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server + twitch_miner.mine( [ Streamer("streamer-username01", settings=StreamerSettings(make_predictions=True , follow_raid=False , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=True, percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.LTE, value=800 ) ) )), From e64e56b00098dbceeb97377edf8a69c44cfdfa1d Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:51:39 +0300 Subject: [PATCH 056/140] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b59b7395..021ab940 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ twitch_miner = TwitchChannelPointsMiner( Priority.DROPS, # - When we don't have anymore watch streak to catch, wait until all drops are collected over the streamers Priority.ORDER # - When we have all of the drops claimed and no watch-streak available, use the order priority (POINTS_ASCENDING, POINTS_DESCEDING) ], - analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption + enable_analytics=False, # Disables Analytics if False. Disabling it significantly reduces memory consumption logger_settings=LoggerSettings( save=True, # If you want to save logs in a file (suggested) console_level=logging.INFO, # Level of logs - use logging.DEBUG for more info @@ -587,7 +587,7 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -### `analytics` option in `twitch_minerfile` toggles Analytics +### `enable_analytics` option in `twitch_minerfile` toggles Analytics Disabling Analytics significantly reduces memory consumption and saves some disk space by not creating and writing `/analytics/*.json`. From 75af7727d76b5a177c9937d6406244f8212b2441 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:55:32 +0300 Subject: [PATCH 057/140] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 021ab940..867a1281 100644 --- a/README.md +++ b/README.md @@ -578,7 +578,7 @@ If you want you can toggle the dark theme with the dedicated checkbox. | ----------- | ---------- | | ![Light theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-light.png) | ![Dark theme](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/chart-analytics-dark.png) | -For use this feature just call the `analytics` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) +For use this feature just call the `analytics()` method before start mining. Read more at: [#96](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/96) The chart will be autofreshed each `refresh` minutes. If you want to connect from one to second machine that have that webpanel you have to use `0.0.0.0` instead of `127.0.0.1`. With the `days_ago` arg you can select how many days you want to show by default in your analytics graph. ```python from TwitchChannelPointsMiner import TwitchChannelPointsMiner @@ -587,7 +587,7 @@ twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # A twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) ``` -### `enable_analytics` option in `twitch_minerfile` toggles Analytics +### `enable_analytics` option in `twitch_minerfile` toggles Analytics needed for the `analytics()` method Disabling Analytics significantly reduces memory consumption and saves some disk space by not creating and writing `/analytics/*.json`. From 8e18174173d3e49a44e1f9666ac9378922348e90 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:54:47 +0300 Subject: [PATCH 058/140] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 430cba53..96d9d21c 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,8 @@ twitch_miner = TwitchChannelPointsMiner( # For example, if in the mine function you don't provide any value for 'make_prediction' but you have set it on TwitchChannelPointsMiner instance, the script will take the value from here. # If you haven't set any value even in the instance the default one will be used +#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server + twitch_miner.mine( [ Streamer("streamer-username01", settings=StreamerSettings(make_predictions=True , follow_raid=False , claim_drops=True , watch_streak=True , bet=BetSettings(strategy=Strategy.SMART , percentage=5 , stealth_mode=True, percentage_gap=20 , max_points=234 , filter_condition=FilterCondition(by=OutcomeKeys.TOTAL_USERS, where=Condition.LTE, value=800 ) ) )), @@ -644,4 +646,4 @@ Now when we did everything we can run miner: `python run.py` Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) ## Disclaimer -This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. \ No newline at end of file +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 15190e252d8bf055f0fadbed4678e8c9dd4db59b Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:04:28 +0300 Subject: [PATCH 059/140] check for updates from my fork + credits --- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/constants.py | 2 +- setup.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 420102cd..30831a19 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "2.1.1" +__version__ = "1.3.4" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 711db889..11ae403b 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -20,7 +20,7 @@ BRANCH = "master" GITHUB_url = ( - "https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/" + "https://raw.githubusercontent.com/rdavydov/Twitch-Channel-Points-Miner-v2/" + BRANCH ) diff --git a/setup.py b/setup.py index 6b04fd79..7c0d41f6 100644 --- a/setup.py +++ b/setup.py @@ -17,12 +17,12 @@ def read(fname): setuptools.setup( name="Twitch-Channel-Points-Miner-v2", version=metadata["version"], - author="Tkd-Alex (Alessandro Maggio)", + author="Tkd-Alex (Alessandro Maggio) and rdavydov (Roman Davydov)", author_email="alex.tkd.alex@gmail.com", description="A simple script that will watch a stream for you and earn the channel points.", license="GPLv3+", keywords="python bot streaming script miner twtich channel-points", - url="https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2", + url="https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2", packages=setuptools.find_packages(), include_package_data=True, install_requires=[ From abdd511574d901982af666917ca725a6bf915edc Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:06:34 +0300 Subject: [PATCH 060/140] my example.py --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96d9d21c..9a40d5b7 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner ``` ## How to use: -First of all please create a run.py file. You can just copy [example.py](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. +First of all please create a run.py file. You can just copy [example.py](https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/blob/master/example.py) and modify it according to your needs. ```python # -*- coding: utf-8 -*- From 7e7fdea673e1d25ed24537e10abe81a31d4a0538 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 28 Oct 2022 17:25:42 +0300 Subject: [PATCH 061/140] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9a40d5b7..15caaa24 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,14 @@ If you want to help with this project, please leave a star 🌟 and share it wit If you want to offer me a coffee, I would be grateful ❤️ -Buy Me A Coffee +Buy Me A Coffee (rdavydov)Buy Me A Coffee | | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| -| Donate BTC | `36GSMYngiiXYqBMnNwYwZc8n6s67LGn4V5` | -| Donate ETH | `0x3cc331b8AB0634CCcfa3bd57E0C625F7E886cAfa` | -| Donate SOL | `pg8Z2VqMVskSEA77g5QqppaQjehGGCWJfVPw9n91AX1` | +|Donate DOGE | `DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` 🤝 rdavydov| +| Donate BTC | `36GSMYngiiXYqBMnNwYwZc8n6s67LGn4V5` 🤝 Tkd-Alex| +| Donate ETH | `0x3cc331b8AB0634CCcfa3bd57E0C625F7E886cAfa` 🤝 Tkd-Alex| +| Donate SOL | `pg8Z2VqMVskSEA77g5QqppaQjehGGCWJfVPw9n91AX1` 🤝 Tkd-Alex| If you have any issues or you want to contribute, you are welcome! But please before read the [CONTRIBUTING.md](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/CONTRIBUTING.md) file. From e51306b27d3d59f3fdaba39671c880428f8a3ac9 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:35:01 +0300 Subject: [PATCH 062/140] temporary login fix (only by browser cookie) --- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/classes/TwitchLogin.py | 11 +++++++---- TwitchChannelPointsMiner/constants.py | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 30831a19..f2acef86 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.3.4" +__version__ = "1.3.5" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index d257a856..6e8adfbb 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -57,8 +57,9 @@ def login_flow(self): "undelete_user": False, "remember_me": True, } - - use_backup_flow = False + # login-fix + #use_backup_flow = False + use_backup_flow = True for attempt in range(0, 25): password = ( @@ -118,7 +119,9 @@ def login_flow(self): # If the user didn't load the password from run.py we can just ask for it again. break - elif err_code == 1000: + # login-fix + #elif err_code == 1000: + elif err_code in [1000, 5022]: logger.info( "Console login unavailable (CAPTCHA solving required)." ) @@ -148,7 +151,7 @@ def set_token(self, new_token): self.session.headers.update({"Authorization": f"Bearer {self.token}"}) def send_login_request(self, json_data): - response = self.session.post("https://passport.twitch.tv/login", json=json_data) + response = self.session.post("https://passport.twitch.tv/protected_login", json=json_data) return response.json() def login_flow_backup(self): diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 11ae403b..e9397c3e 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -3,7 +3,8 @@ IRC = "irc.chat.twitch.tv" IRC_PORT = 6667 WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" -CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" +CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App +#CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" From 894993bae1f75d5d02b79833856c70f25a246ec0 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:48:51 +0300 Subject: [PATCH 063/140] isBadBot checking + client_id comments --- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/classes/Twitch.py | 25 +++++++++++++++++++ .../classes/TwitchLogin.py | 4 ++- TwitchChannelPointsMiner/constants.py | 1 + 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index f2acef86..bad76d5f 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.3.5" +__version__ = "1.3.6" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c2867a14..f0feb0cf 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -15,6 +15,9 @@ from pathlib import Path from secrets import choice, token_hex +import json +from base64 import urlsafe_b64decode + import requests from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign @@ -302,12 +305,34 @@ def post_integrity(self): f"Data: [], Status code: {response.status_code}, Content: {response.text}" ) self.integrity = response.json().get("token", None) + #logger.info(f"integrity: {self.integrity}") + + if self.isBadBot(self.integrity) is True: + logger.error("Uh-oh, Twitch has detected this miner as a \"Bad Bot\"") + self.integrity_expire = response.json().get("expiration", 0) + #logger.info(f"integrity_expire: {self.integrity_expire}") return self.integrity except requests.exceptions.RequestException as e: logger.error(f"Error with post_integrity: {e}") return self.integrity + # verify the integrity token's contents for the "is_bad_bot" flag + def isBadBot(self, integrity): + stripped_token: str = self.integrity.split('.')[2] + "==" + messy_json: str = urlsafe_b64decode(stripped_token.encode()).decode(errors="ignore") + match = re.search(r'(.+)(?<="}).+$', messy_json) + if match is None: + #raise MinerException("Unable to parse the integrity token") + logger.error("Unable to parse the integrity token") + return + decoded_header: JsonType = json.loads(match.group(1)) + #logger.info(f"decoded_header: {decoded_header}") + if decoded_header.get("is_bad_bot", "false") != "false": + return True + else: + return False + def update_client_version(self): try: response = requests.get(URL) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 6e8adfbb..13a76e24 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -171,8 +171,10 @@ def login_flow_backup(self): if browser == "1": # chrome cookie_jar = browser_cookie3.chrome(domain_name=twitch_domain) else: - cookie_jar = browser_cookie3.firefox(domain_name=twitch_domain) + cookie_jar = browser_cookie3.firefox(domain_name=twitch_domain) + #logger.info(f"cookie_jar: {cookie_jar}") cookies_dict = requests.utils.dict_from_cookiejar(cookie_jar) + #logger.info(f"cookies_dict: {cookies_dict}") self.username = cookies_dict.get("login") return cookies_dict.get("auth-token") diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index e9397c3e..9b9e7f9f 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -3,6 +3,7 @@ IRC = "irc.chat.twitch.tv" IRC_PORT = 6667 WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" +#CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" # Browser CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App #CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" From 7b435c1e5961d6899df2e2cf496a4d0c30b6b571 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:57:52 +0300 Subject: [PATCH 064/140] script to view contents of a cookie file --- pickle_view.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 pickle_view.py diff --git a/pickle_view.py b/pickle_view.py new file mode 100644 index 00000000..b77a577b --- /dev/null +++ b/pickle_view.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +# Simple script to view contents of a cookie file stored in a pickle format + +import pickle +import sys + +if __name__ == '__main__': + argv = sys.argv + if len(argv) <= 1: + print("Specify a pickle file as a parameter, e.g. cookies/user.pkl") + else: + print(pickle.load(open(argv[1], 'rb'))) \ No newline at end of file From 3d3f8103a9e3d123512006fea51695f7edebf1a4 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sun, 30 Oct 2022 20:03:13 +0300 Subject: [PATCH 065/140] full cookies for selenium --- TwitchChannelPointsMiner/classes/TwitchLogin.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 13a76e24..e348f0dc 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -19,7 +19,6 @@ logger = logging.getLogger(__name__) - class TwitchLogin(object): __slots__ = [ "client_id", @@ -32,6 +31,7 @@ class TwitchLogin(object): "user_id", "email", "cookies", + "shared_cookies" ] def __init__(self, client_id, username, user_agent, password=None): @@ -48,6 +48,7 @@ def __init__(self, client_id, username, user_agent, password=None): self.email = None self.cookies = [] + self.shared_cookies = [] def login_flow(self): logger.info("You'll have to login to Twitch!") @@ -176,6 +177,7 @@ def login_flow_backup(self): cookies_dict = requests.utils.dict_from_cookiejar(cookie_jar) #logger.info(f"cookies_dict: {cookies_dict}") self.username = cookies_dict.get("login") + self.shared_cookies = cookies_dict return cookies_dict.get("auth-token") def check_login(self): @@ -188,14 +190,19 @@ def check_login(self): return self.login_check_result def save_cookies(self, cookies_file): - cookies_dict = self.session.cookies.get_dict() - cookies_dict["auth-token"] = self.token - if "persistent" not in cookies_dict: # saving user id cookies - cookies_dict["persistent"] = self.user_id + #cookies_dict = self.session.cookies.get_dict() + #print(f"cookies_dict2pickle: {cookies_dict}") + #cookies_dict["auth-token"] = self.token + #if "persistent" not in cookies_dict: # saving user id cookies + # cookies_dict["persistent"] = self.user_id + # old way saves only 'auth-token' and 'persistent' self.cookies = [] + cookies_dict = self.shared_cookies + #print(f"cookies_dict2pickle: {cookies_dict}") for cookie_name, value in cookies_dict.items(): self.cookies.append({"name": cookie_name, "value": value}) + #print(f"cookies2pickle: {self.cookies}") pickle.dump(self.cookies, open(cookies_file, "wb")) def get_cookie_value(self, key): From 433667ee26f872547cc5142bbfec7586e888d10d Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 31 Oct 2022 16:16:38 +0300 Subject: [PATCH 066/140] uc will go here --- .../classes/TwitchLogin.py | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index e348f0dc..e5d0a8e1 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -8,7 +8,7 @@ import os import pickle -import browser_cookie3 +#import browser_cookie3 import requests from TwitchChannelPointsMiner.classes.Exceptions import ( @@ -157,18 +157,46 @@ def send_login_request(self, json_data): def login_flow_backup(self): """Backup OAuth login flow in case manual captcha solving is required""" - browser = input( - "What browser do you use? Chrome (1), Firefox (2), Other (3): " - ).strip() - if browser not in ("1", "2"): - logger.info("Your browser is unsupported, sorry.") - return None - - input( - "Please login inside your browser of choice (NOT incognito mode) and press Enter..." + from undetected_chromedriver import ChromeOptions + import seleniumwire.undetected_chromedriver.v2 as uc + + HEADLESS = False + SLEEP_TIME = 10 + + options = uc.ChromeOptions() + if HEADLESS is True: + options.add_argument('--headless') + options.add_argument('--log-level=3') + options.add_argument('--disable-web-security') + options.add_argument('--allow-running-insecure-content') + options.add_argument('--lang=en') + options.add_argument('--no-sandbox') + options.add_argument('--disable-gpu') + #options.add_argument("--user-agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"") + #options.add_argument("--window-size=1920,1080") + #options.set_capability("detach", True) + + driver = uc.Chrome( + options=options, use_subprocess=True#, executable_path=EXECUTABLE_PATH ) + driver.get('https://www.twitch.tv/settings/profile') + cookies = pickle.load(open(self.cookies_file, "rb")) + for cookie in cookies: + driver.add_cookie(cookie) + driver.get('https://www.twitch.tv/settings/profile') + + request = driver.wait_for_request("https://gql.twitch.tv/integrity", timeout=20) + + body = request.response.body.decode("UTF-8") + driver.quit() + integrity_json = json.loads(body) + + self.integrity = integrity_json.get("token", None) + logger.info(f"integrity: {self.integrity}") +################################################# logger.info("Loading cookies saved on your computer...") twitch_domain = ".twitch.tv" + if browser == "1": # chrome cookie_jar = browser_cookie3.chrome(domain_name=twitch_domain) else: From efca4bd78e0a6c31a6170e61d063108c0d2e5e13 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 31 Oct 2022 19:03:44 +0300 Subject: [PATCH 067/140] working beautifully --- .../TwitchChannelPointsMiner.py | 1 + TwitchChannelPointsMiner/__init__.py | 2 +- .../classes/TwitchLogin.py | 85 +++++++++---------- TwitchChannelPointsMiner/constants.py | 3 +- requirements.txt | 3 + 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 6710115f..941d4a10 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -43,6 +43,7 @@ logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("irc.client").setLevel(logging.ERROR) +logging.getLogger("seleniumwire").setLevel(logging.ERROR) logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index bad76d5f..8fe97a21 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.3.6" +__version__ = "1.4.0" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index e5d0a8e1..8fbf790e 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -8,17 +8,29 @@ import os import pickle -#import browser_cookie3 import requests from TwitchChannelPointsMiner.classes.Exceptions import ( BadCredentialsException, WrongCookiesException, ) -from TwitchChannelPointsMiner.constants import GQLOperations +from TwitchChannelPointsMiner.constants import CLIENT_ID, GQLOperations logger = logging.getLogger(__name__) +def interceptor(request) -> str: + if ( + request.method == 'POST' + and request.url == 'https://passport.twitch.tv/protected_login' + ): + import json + body = request.body.decode('utf-8') + data = json.loads(body) + data['client_id'] = CLIENT_ID + request.body = json.dumps(data).encode('utf-8') + del request.headers['Content-Length'] + request.headers['Content-Length'] = str(len(request.body)) + class TwitchLogin(object): __slots__ = [ "client_id", @@ -30,8 +42,7 @@ class TwitchLogin(object): "password", "user_id", "email", - "cookies", - "shared_cookies" + "cookies" ] def __init__(self, client_id, username, user_agent, password=None): @@ -48,7 +59,6 @@ def __init__(self, client_id, username, user_agent, password=None): self.email = None self.cookies = [] - self.shared_cookies = [] def login_flow(self): logger.info("You'll have to login to Twitch!") @@ -156,12 +166,13 @@ def send_login_request(self, json_data): return response.json() def login_flow_backup(self): - """Backup OAuth login flow in case manual captcha solving is required""" + """Backup OAuth Selenium login""" from undetected_chromedriver import ChromeOptions import seleniumwire.undetected_chromedriver.v2 as uc + from selenium.webdriver.common.by import By + from time import sleep HEADLESS = False - SLEEP_TIME = 10 options = uc.ChromeOptions() if HEADLESS is True: @@ -176,37 +187,34 @@ def login_flow_backup(self): #options.add_argument("--window-size=1920,1080") #options.set_capability("detach", True) + logger.info('Now a browser window will open, it will login with your data.') driver = uc.Chrome( options=options, use_subprocess=True#, executable_path=EXECUTABLE_PATH ) - driver.get('https://www.twitch.tv/settings/profile') - cookies = pickle.load(open(self.cookies_file, "rb")) - for cookie in cookies: - driver.add_cookie(cookie) - driver.get('https://www.twitch.tv/settings/profile') + driver.request_interceptor = interceptor + driver.get('https://www.twitch.tv/login') + + driver.find_element(By.ID, 'login-username').send_keys(self.username) + driver.find_element(By.ID, 'password-input').send_keys(self.password) + sleep(0.3) + driver.execute_script( + 'document.querySelector("#root > div > div.scrollable-area > div.simplebar-scroll-content > div > div > div > div.Layout-sc-nxg1ff-0.gZaqky > form > div > div:nth-child(3) > button > div > div").click()' + ) + + logger.info( + 'Enter your verification code in the browser and wait for the Twitch website to load, then press Enter here.' + ) + input() - request = driver.wait_for_request("https://gql.twitch.tv/integrity", timeout=20) + logger.info("Extracting cookies...") - body = request.response.body.decode("UTF-8") + self.cookies = driver.get_cookies() driver.quit() - integrity_json = json.loads(body) - - self.integrity = integrity_json.get("token", None) - logger.info(f"integrity: {self.integrity}") -################################################# - logger.info("Loading cookies saved on your computer...") - twitch_domain = ".twitch.tv" - - if browser == "1": # chrome - cookie_jar = browser_cookie3.chrome(domain_name=twitch_domain) - else: - cookie_jar = browser_cookie3.firefox(domain_name=twitch_domain) - #logger.info(f"cookie_jar: {cookie_jar}") - cookies_dict = requests.utils.dict_from_cookiejar(cookie_jar) - #logger.info(f"cookies_dict: {cookies_dict}") - self.username = cookies_dict.get("login") - self.shared_cookies = cookies_dict - return cookies_dict.get("auth-token") + self.username = self.get_cookie_value("login") + + return self.get_cookie_value("auth-token") + + def check_login(self): if self.login_check_result: @@ -218,19 +226,6 @@ def check_login(self): return self.login_check_result def save_cookies(self, cookies_file): - #cookies_dict = self.session.cookies.get_dict() - #print(f"cookies_dict2pickle: {cookies_dict}") - #cookies_dict["auth-token"] = self.token - #if "persistent" not in cookies_dict: # saving user id cookies - # cookies_dict["persistent"] = self.user_id - - # old way saves only 'auth-token' and 'persistent' - self.cookies = [] - cookies_dict = self.shared_cookies - #print(f"cookies_dict2pickle: {cookies_dict}") - for cookie_name, value in cookies_dict.items(): - self.cookies.append({"name": cookie_name, "value": value}) - #print(f"cookies2pickle: {self.cookies}") pickle.dump(self.cookies, open(cookies_file, "wb")) def get_cookie_value(self, key): diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 9b9e7f9f..26154b0b 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -11,7 +11,8 @@ USER_AGENTS = { "Windows": { - "CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", + #"CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", + 'CHROME': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", "FIREFOX": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", }, "Linux": { diff --git a/requirements.txt b/requirements.txt index 776b3e24..d85d68c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,6 @@ colorama flask irc pandas +selenium +selenium-wire +undetected_chromedriver From 2aaa65256de248365793a6356ff67f89f1e71254 Mon Sep 17 00:00:00 2001 From: antipatico Date: Tue, 1 Nov 2022 22:26:14 +0100 Subject: [PATCH 068/140] python-ify IF statement --- 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 f0feb0cf..59681ecc 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -83,7 +83,7 @@ def __init__(self, username, user_agent, password=None): ) def login(self): - if os.path.isfile(self.cookies_file) is False: + if not os.path.isfile(self.cookies_file): if self.twitch_login.login_flow(): self.twitch_login.save_cookies(self.cookies_file) else: From 4d1d3d1bdb4a2289316eb935f3217b1cf5d8029c Mon Sep 17 00:00:00 2001 From: antipatico Date: Tue, 1 Nov 2022 22:26:38 +0100 Subject: [PATCH 069/140] fix crash on selenium login when password is None --- TwitchChannelPointsMiner/classes/TwitchLogin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 8fbf790e..30d89f76 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -152,7 +152,7 @@ def login_flow(self): break if use_backup_flow: - self.set_token(self.login_flow_backup()) + self.set_token(self.login_flow_backup(password)) return self.check_login() return False @@ -165,7 +165,7 @@ def send_login_request(self, json_data): response = self.session.post("https://passport.twitch.tv/protected_login", json=json_data) return response.json() - def login_flow_backup(self): + def login_flow_backup(self, password = None): """Backup OAuth Selenium login""" from undetected_chromedriver import ChromeOptions import seleniumwire.undetected_chromedriver.v2 as uc @@ -195,7 +195,7 @@ def login_flow_backup(self): driver.get('https://www.twitch.tv/login') driver.find_element(By.ID, 'login-username').send_keys(self.username) - driver.find_element(By.ID, 'password-input').send_keys(self.password) + driver.find_element(By.ID, 'password-input').send_keys(password) sleep(0.3) driver.execute_script( 'document.querySelector("#root > div > div.scrollable-area > div.simplebar-scroll-content > div > div > div > div.Layout-sc-nxg1ff-0.gZaqky > form > div > div:nth-child(3) > button > div > div").click()' From 3a33f494dacf59b8a99403c699be99405344f08e Mon Sep 17 00:00:00 2001 From: antipatico Date: Tue, 1 Nov 2022 22:35:55 +0100 Subject: [PATCH 070/140] updated python from 3.8 to 3.11 in docker container --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6bb7068f..f3e9a2d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-slim-buster +FROM python:3.11.0-slim-buster ARG BUILDX_QEMU_ENV From 31baf175789d57236dfeb633dad3232ff14630f1 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 4 Nov 2022 13:33:08 +0300 Subject: [PATCH 071/140] no need for browser_cookie3 --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d85d68c6..44859fce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ requests websocket-client -browser_cookie3 pillow python-dateutil emoji From 575b7f0e5c2f4a383d00c4c6f0706c8851c691ca Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 4 Nov 2022 13:37:16 +0300 Subject: [PATCH 072/140] update install_requires in setup.py --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7c0d41f6..8413bf8a 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,6 @@ def read(fname): install_requires=[ "requests", "websocket-client", - "browser_cookie3", "pillow", "python-dateutil", "emoji", @@ -38,6 +37,9 @@ def read(fname): "flask", "irc", "pandas", + "selenium", + "selenium-wire", + "undetected_chromedriver" ], long_description=read("README.md"), long_description_content_type="text/markdown", From 61fbc80542a169f4012fbf7dffc4756cdc685953 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 4 Nov 2022 21:21:00 +0300 Subject: [PATCH 073/140] badbot error to info --- TwitchChannelPointsMiner/classes/Twitch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 59681ecc..8bae80f3 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -308,7 +308,7 @@ def post_integrity(self): #logger.info(f"integrity: {self.integrity}") if self.isBadBot(self.integrity) is True: - logger.error("Uh-oh, Twitch has detected this miner as a \"Bad Bot\"") + logger.info("Uh-oh, Twitch has detected this miner as a \"Bad Bot\". Don't worry.") self.integrity_expire = response.json().get("expiration", 0) #logger.info(f"integrity_expire: {self.integrity_expire}") @@ -324,7 +324,7 @@ def isBadBot(self, integrity): match = re.search(r'(.+)(?<="}).+$', messy_json) if match is None: #raise MinerException("Unable to parse the integrity token") - logger.error("Unable to parse the integrity token") + logger.info("Unable to parse the integrity token. Don't worry.") return decoded_header: JsonType = json.loads(match.group(1)) #logger.info(f"decoded_header: {decoded_header}") From 5f8217de08b9ed61d25cd24b1d123a98956914a9 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 7 Nov 2022 23:54:32 +0300 Subject: [PATCH 074/140] 1.4.1 --- TwitchChannelPointsMiner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 8fe97a21..64f7d1bb 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.4.0" +__version__ = "1.4.1" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ From b4502d81469c7db7e0f208f95e6e26148a01a5b6 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 12 Nov 2022 23:09:24 +0300 Subject: [PATCH 075/140] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 15caaa24..0b54762b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ If you want to offer me a coffee, I would be grateful ❤️ | | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| |Donate DOGE | `DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` 🤝 rdavydov| +|Donate XMR | `46fzadEigE7B3kyJB6AdiccaTha3SWUdTNnE4FL6YtjCgYMASAyXGkMe1XY4iApv2VDSxBT6d8PTW3vwtNWnfu6W4g4jyJF` 🤝 rdavydov| | Donate BTC | `36GSMYngiiXYqBMnNwYwZc8n6s67LGn4V5` 🤝 Tkd-Alex| | Donate ETH | `0x3cc331b8AB0634CCcfa3bd57E0C625F7E886cAfa` 🤝 Tkd-Alex| | Donate SOL | `pg8Z2VqMVskSEA77g5QqppaQjehGGCWJfVPw9n91AX1` 🤝 Tkd-Alex| From e81bf00089bf1f6ac4622a7646b09206e9ff2d44 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 12 Nov 2022 23:18:30 +0300 Subject: [PATCH 076/140] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b54762b..fe7cdda5 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,15 @@ If you want to help with this project, please leave a star 🌟 and share it wit If you want to offer me a coffee, I would be grateful ❤️ -Buy Me A Coffee (rdavydov)Buy Me A Coffee +| | | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +|Buy Me A Coffee (rdavydov)|Buy Me A Coffee| +|🤝 rdavydov|🤝 Tkd-Alex| | | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| -|Donate DOGE | `DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` 🤝 rdavydov| -|Donate XMR | `46fzadEigE7B3kyJB6AdiccaTha3SWUdTNnE4FL6YtjCgYMASAyXGkMe1XY4iApv2VDSxBT6d8PTW3vwtNWnfu6W4g4jyJF` 🤝 rdavydov| +|Donate DOGE | `DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` _(DOGE)_ 🤝 rdavydov| +|Donate XMR | `46fzadEigE7B3kyJB6AdiccaTha3SWUdTNnE4FL6YtjCgYMASAyXGkMe1XY4iApv2VDSxBT6d8PTW3vwtNWnfu6W4g4jyJF` _(XMR)_ 🤝 rdavydov| | Donate BTC | `36GSMYngiiXYqBMnNwYwZc8n6s67LGn4V5` 🤝 Tkd-Alex| | Donate ETH | `0x3cc331b8AB0634CCcfa3bd57E0C625F7E886cAfa` 🤝 Tkd-Alex| | Donate SOL | `pg8Z2VqMVskSEA77g5QqppaQjehGGCWJfVPw9n91AX1` 🤝 Tkd-Alex| From ca46ab5fbf19a83d3a4a11b7ca2e5a26eae40862 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 12 Nov 2022 23:33:33 +0300 Subject: [PATCH 077/140] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe7cdda5..c68b8994 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ If you want to offer me a coffee, I would be grateful ❤️ | | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| |Donate DOGE | `DAKzncwKkpfPCm1xVU7u2pConpXwX7HS3D` _(DOGE)_ 🤝 rdavydov| -|Donate XMR | `46fzadEigE7B3kyJB6AdiccaTha3SWUdTNnE4FL6YtjCgYMASAyXGkMe1XY4iApv2VDSxBT6d8PTW3vwtNWnfu6W4g4jyJF` _(XMR)_ 🤝 rdavydov| +|Donate XMR | `46fzadEigE7B3kyJB6AdiccaTha3SWUdTNnE4FL6YtjCgYMASAyXGkMe1XY4iApv2VDSxBT6d8PTW3vwtNWnfu6W4g4jyJF` _(XMR)_ 🤝 rdavydov| | Donate BTC | `36GSMYngiiXYqBMnNwYwZc8n6s67LGn4V5` 🤝 Tkd-Alex| | Donate ETH | `0x3cc331b8AB0634CCcfa3bd57E0C625F7E886cAfa` 🤝 Tkd-Alex| | Donate SOL | `pg8Z2VqMVskSEA77g5QqppaQjehGGCWJfVPw9n91AX1` 🤝 Tkd-Alex| From f0b77cb52a010e8b3fa0106c117fbf3cde768090 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:57:24 +0300 Subject: [PATCH 078/140] add checks to fix 'ERR_BADAUTH' --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 7 +++++++ TwitchChannelPointsMiner/classes/TwitchLogin.py | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 941d4a10..5541b8f0 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -287,6 +287,13 @@ def run( # Subscribe to community-points-user. Get update for points spent or gains user_id = self.twitch.twitch_login.get_user_id() + #print(f"!!!!!!!!!!!!!! USER_ID: {user_id}") + + # Fixes 'ERR_BADAUTH' + if not user_id: + logger.error("No user_id, exiting...") + self.end(0, 0) + self.ws_pool.submit( PubsubTopic( "community-points-user-v1", diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 30d89f76..4e7b2fd8 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -207,14 +207,18 @@ def login_flow_backup(self, password = None): input() logger.info("Extracting cookies...") - self.cookies = driver.get_cookies() + #print(self.cookies) + #driver.close() driver.quit() self.username = self.get_cookie_value("login") + #print(f"self.username: {self.username}") - return self.get_cookie_value("auth-token") - + if not self.username: + logger.error("Couldn't extract login, probably bad cookies.") + return False + return self.get_cookie_value("auth-token") def check_login(self): if self.login_check_result: From 214f0c3e9bbdc0dfb165cef151a5957c6e63bb04 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 00:04:31 +0300 Subject: [PATCH 079/140] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c68b8994..ee3dbb03 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -![Twitch Channel Points Miner - v2](https://raw.githubusercontent.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/master/assets/banner.png) +![Twitch Channel Points Miner - v2](https://raw.githubusercontent.com/rdavydov/Twitch-Channel-Points-Miner-v2/master/assets/banner.png)

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

**Credits** From 76dcb6cf9bee6ae6272a41436b310670dfdc64e6 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:38:30 +0300 Subject: [PATCH 080/140] Update deploy-docker.yml --- .github/workflows/deploy-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 297b16ca..002a669f 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -32,7 +32,7 @@ jobs: context: . push: true tags: | - tkdalex/twitch-channel-points-miner-v2:latest + rdavidoff/twitch-channel-points-miner-v2:latest platforms: linux/amd64,linux/arm64,linux/arm/v7 build-args: BUILDX_QEMU_ENV=true @@ -42,7 +42,7 @@ jobs: # with: # username: ${{ secrets.DOCKER_USERNAME }} # password: ${{ secrets.DOCKER_TOKEN }} - # repository: tkdalex/twitch-channel-points-miner-v2 + # repository: rdavidoff/twitch-channel-points-miner-v2 - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} From 4547d1ad13ca30d180c9dc2b22936ffe8f51982e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:54:53 +0300 Subject: [PATCH 081/140] Update README.md --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ee3dbb03..f3a55583 100644 --- a/README.md +++ b/README.md @@ -311,10 +311,10 @@ pip install -r requirements.txt Start mining! `python run.py` 🥳 -### pip +### pip Install the package via pip, you will find a stable version - maybe a different version from the master branch. - `pip install Twitch-Channel-Points-Miner-v2` -- Exceute the run.py file `python run.py` 🥳 +- Exceute the run.py file `python run.py` 🥳 ### Docker @@ -335,7 +335,7 @@ version: "3.9" services: miner: - image: tkdalex/twitch-channel-points-miner-v2 + image: rdavidoff/twitch-channel-points-miner-v2 stdin_open: true tty: true environment: @@ -357,18 +357,25 @@ docker run \ -v $(pwd)/logs:/usr/src/app/logs \ -v $(pwd)/run.py:/usr/src/app/run.py:ro \ -p 5000:5000 \ - tkdalex/twitch-channel-points-miner-v2 + rdavidoff/twitch-channel-points-miner-v2 ``` `$(pwd)` Could not work on Windows (cmd), please use the absolute path instead, like: `/path/of/your/cookies:/usr/src/app/cookies`. + +The correct solution for Windows lies in the correct command line: `docker run -v C:\Absolute\Path\To\Twitch-Channel-Points-Miner-v2\run.py:/usr/src/app/run.py:ro rdavidoff/twitch-channel-points-miner-v2`. + +`run.py` MUST be mounted as a volume (`-v`). + If you don't mount the volume for the analytics (or cookies or logs) folder, the folder will be automatically created on the Docker container, and you will lose all the data when it is stopped. -If you don't have a cookie or It's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like + +If you don't have a cookie or it's your first time running the script, you will need to login to Twitch and start the container with `-it` args. If you need to run multiple containers you can bind different ports (only if you need also the analytics) and mount dirrent run.py file, like + ```sh -docker run --name user1-v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 tkdalex/twitch-channel-points-miner-v2 +docker run --name user1 -v $(pwd)/user1.py:/usr/src/app/run.py:ro -p 5001:5000 rdavidoff/twitch-channel-points-miner-v2 ``` ```sh -docker run --name user2-v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 tkdalex/twitch-channel-points-miner-v2 +docker run --name user2 -v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 rdavidoff/twitch-channel-points-miner-v2 ``` About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). From c26fc492dd5205e3e7a850ca49b355d1ed474ae8 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:58:45 +0300 Subject: [PATCH 082/140] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f3a55583..0eef2045 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ twitch_miner.mine(followers=True, blacklist=["user1", "user2"]) # Blacklist exa ``` ### By cloning the repository -1. Clone this repository `git clone https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2` +1. Clone this repository `git clone https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2` 2. Install all the requirements `pip install -r requirements.txt` . If you have problems with requirements, make sure to have at least Python3.6. You could also try to create a _virtualenv_ and then install all the requirements ```sh pip install virtualenv @@ -311,8 +311,8 @@ pip install -r requirements.txt Start mining! `python run.py` 🥳 -### pip -Install the package via pip, you will find a stable version - maybe a different version from the master branch. +### pip deprecated +Install the package via pip, you will find a stable version - maybe a different version from the master branch. - `pip install Twitch-Channel-Points-Miner-v2` - Exceute the run.py file `python run.py` 🥳 From edce76e76b9c75a13ee37c8eead529a91ee795b8 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 06:03:28 +0300 Subject: [PATCH 083/140] fix undefined name 'JsonType' --- 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 8bae80f3..745fda91 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -326,7 +326,7 @@ def isBadBot(self, integrity): #raise MinerException("Unable to parse the integrity token") logger.info("Unable to parse the integrity token. Don't worry.") return - decoded_header: JsonType = json.loads(match.group(1)) + decoded_header = json.loads(match.group(1)) #logger.info(f"decoded_header: {decoded_header}") if decoded_header.get("is_bad_bot", "false") != "false": return True From c3bea5c1ed27737144177e6bfcc8ffb6676f774e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:25:01 +0300 Subject: [PATCH 084/140] fix 'Rust 1.41.1 does not match >=1.48.0' --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6bb7068f..8a7b2be3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ WORKDIR /usr/src/app COPY ./requirements.txt ./ +ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 + RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ From bf4b8b8b57953c2a6b8c13ba7f2f979c47729824 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:17:30 +0300 Subject: [PATCH 085/140] new UA, login fix (only console) --- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/classes/Twitch.py | 15 ++++--- .../classes/TwitchLogin.py | 45 ++++++++++++++----- TwitchChannelPointsMiner/constants.py | 3 ++ TwitchChannelPointsMiner/utils.py | 7 +-- requirements.txt | 3 -- setup.py | 5 +-- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 64f7d1bb..6d732f0b 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.4.1" +__version__ = "1.5.0" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 745fda91..17d1873e 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -67,13 +67,13 @@ def __init__(self, username, user_agent, password=None): 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, password=password - ) - self.running = True self.device_id = "".join( choice(string.ascii_letters + string.digits) for _ in range(32) ) + self.twitch_login = TwitchLogin( + CLIENT_ID, self.device_id, username, self.user_agent, password=password + ) + self.running = True self.integrity = None self.integrity_expire = 0 self.client_session = token_hex(16) @@ -126,9 +126,14 @@ def update_stream(self, streamer): def get_spade_url(self, streamer): try: - headers = {"User-Agent": self.user_agent} + # fixes AttributeError: 'NoneType' object has no attribute 'group' + #headers = {"User-Agent": self.user_agent} + from TwitchChannelPointsMiner.constants import USER_AGENTS + headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + main_page_request = requests.get(streamer.streamer_url, headers=headers) response = main_page_request.text + #logger.info(response) regex_settings = "(https://static.twitchcdn.net/config/settings.*?js)" settings_url = re.search(regex_settings, response).group(1) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index 4e7b2fd8..a53e5ef6 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -def interceptor(request) -> str: +"""def interceptor(request) -> str: if ( request.method == 'POST' and request.url == 'https://passport.twitch.tv/protected_login' @@ -29,11 +29,12 @@ def interceptor(request) -> str: data['client_id'] = CLIENT_ID request.body = json.dumps(data).encode('utf-8') del request.headers['Content-Length'] - request.headers['Content-Length'] = str(len(request.body)) + request.headers['Content-Length'] = str(len(request.body))""" class TwitchLogin(object): __slots__ = [ "client_id", + "device_id", "token", "login_check_result", "session", @@ -45,13 +46,14 @@ class TwitchLogin(object): "cookies" ] - def __init__(self, client_id, username, user_agent, password=None): + def __init__(self, client_id, device_id, username, user_agent, password=None): self.client_id = client_id + self.device_id = device_id self.token = None self.login_check_result = False self.session = requests.session() self.session.headers.update( - {"Client-ID": self.client_id, "User-Agent": user_agent} + { "Client-ID": self.client_id, "X-Device-Id": self.device_id, "User-Agent": user_agent } ) self.username = username self.password = password @@ -69,8 +71,8 @@ def login_flow(self): "remember_me": True, } # login-fix - #use_backup_flow = False - use_backup_flow = True + use_backup_flow = False + #use_backup_flow = True for attempt in range(0, 25): password = ( @@ -131,8 +133,8 @@ def login_flow(self): # If the user didn't load the password from run.py we can just ask for it again. break # login-fix - #elif err_code == 1000: - elif err_code in [1000, 5022]: + elif err_code == 1000: + #elif err_code in [1000, 5022]: logger.info( "Console login unavailable (CAPTCHA solving required)." ) @@ -162,11 +164,18 @@ def set_token(self, new_token): self.session.headers.update({"Authorization": f"Bearer {self.token}"}) def send_login_request(self, json_data): - response = self.session.post("https://passport.twitch.tv/protected_login", json=json_data) + #response = self.session.post("https://passport.twitch.tv/protected_login", json=json_data) + response = self.session.post("https://passport.twitch.tv/login", json=json_data, headers={ + 'Accept': 'application/vnd.twitchtv.v3+json', + 'Accept-Encoding': 'gzip', + 'Accept-Language': 'en-US', + 'Content-Type': 'application/json; charset=UTF-8', + 'Host': 'passport.twitch.tv' + },) return response.json() def login_flow_backup(self, password = None): - """Backup OAuth Selenium login""" + """Backup OAuth Selenium login from undetected_chromedriver import ChromeOptions import seleniumwire.undetected_chromedriver.v2 as uc from selenium.webdriver.common.by import By @@ -218,7 +227,9 @@ def login_flow_backup(self, password = None): logger.error("Couldn't extract login, probably bad cookies.") return False - return self.get_cookie_value("auth-token") + return self.get_cookie_value("auth-token")""" + logger.error("Backup login flow is not available. Use a VPN or wait a while to avoid the CAPTCHA.") + return False def check_login(self): if self.login_check_result: @@ -230,7 +241,17 @@ def check_login(self): return self.login_check_result def save_cookies(self, cookies_file): - pickle.dump(self.cookies, open(cookies_file, "wb")) + #pickle.dump(self.cookies, open(cookies_file, "wb")) + # ^ only this line was needed with Selenium ^ + cookies_dict = self.session.cookies.get_dict() + cookies_dict["auth-token"] = self.token + if "persistent" not in cookies_dict: # saving user id cookies + cookies_dict["persistent"] = self.user_id + + self.cookies = [] + for cookie_name, value in cookies_dict.items(): + self.cookies.append({"name": cookie_name, "value": value}) + pickle.dump(self.cookies, open(cookies_file, "wb")) def get_cookie_value(self, key): for cookie in self.cookies: diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 26154b0b..e81966ca 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -19,6 +19,9 @@ "CHROME": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", }, + "Android": { + "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" + } } BRANCH = "master" diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index ea0b6515..fd88c5a5 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -53,12 +53,13 @@ def create_nonce(length=30) -> str: nonce += char return nonce - +# for mobile-token def get_user_agent(browser: str) -> str: - try: + """try: return USER_AGENTS[platform.system()][browser] except KeyError: - return USER_AGENTS["Linux"]["FIREFOX"] + return USER_AGENTS["Linux"]["FIREFOX"]""" + return USER_AGENTS["Android"]["App"] def remove_emoji(string: str) -> str: diff --git a/requirements.txt b/requirements.txt index 44859fce..5a011c2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,3 @@ colorama flask irc pandas -selenium -selenium-wire -undetected_chromedriver diff --git a/setup.py b/setup.py index 8413bf8a..30afcecd 100644 --- a/setup.py +++ b/setup.py @@ -36,10 +36,7 @@ def read(fname): "colorama", "flask", "irc", - "pandas", - "selenium", - "selenium-wire", - "undetected_chromedriver" + "pandas" ], long_description=read("README.md"), long_description_content_type="text/markdown", From a643ac646ec6c042fa97a040d43e5f83d363a88f Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:25:06 +0300 Subject: [PATCH 086/140] Delete code-checker.yml --- .github/workflows/code-checker.yml | 38 ------------------------------ 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/code-checker.yml diff --git a/.github/workflows/code-checker.yml b/.github/workflows/code-checker.yml deleted file mode 100644 index 2aaa707b..00000000 --- a/.github/workflows/code-checker.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: code-checker - -on: - push: - branches: [master] - pull_request: - -jobs: - static-check-lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: [3.6, 3.7, 3.8] - steps: - - name: Clone Repository - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4.1.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Requirements - run: | - pip3 install --upgrade pip - pip3 install pyflakes - pip3 install black - pip3 install isort - - - name: Detect errors with pyflakes - run: pyflakes TwitchChannelPointsMiner - - - name: Lint with black - run: black TwitchChannelPointsMiner --check --diff - - - name: Lint with isort - run: isort TwitchChannelPointsMiner --profile black --check --diff From 06fbdd697d0596be6b4d8873ba168e1026f3bc63 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 19:54:55 +0300 Subject: [PATCH 087/140] upgrading pip may fix hanging hangs on Building wheel for pandas --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8a7b2be3..7d62b534 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ COPY ./requirements.txt ./ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 +RUN pip install --upgrade pip + RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ From 4acbb1224a5680b6458d693c9ab12af66a6fd12f Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 20:33:52 +0300 Subject: [PATCH 088/140] Create Dockerfile.arm32v7 --- Dockerfile.arm32v7 | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Dockerfile.arm32v7 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 new file mode 100644 index 00000000..05126f96 --- /dev/null +++ b/Dockerfile.arm32v7 @@ -0,0 +1,39 @@ +FROM python:3.8-slim-buster + +ARG BUILDX_QEMU_ENV + +WORKDIR /usr/src/app + +COPY ./requirements.txt ./ + +ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 + +RUN pip install --upgrade pip + +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ + gcc \ + libffi-dev \ + rustc \ + zlib1g-dev \ + libjpeg-dev \ + libssl-dev \ + make \ + automake \ + g++ \ + subversion \ + python3-dev \ + && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ + pip install -U cryptography==3.3.2; \ + fi \ + && pip install --index-url=https://www.piwheels.org/simple --no-cache-dir -r requirements.txt \ + && pip cache purge \ + && apt-get remove -y gcc rustc \ + && apt-get autoremove -y \ + && apt-get autoclean -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /usr/share/doc/* + +ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner +ENTRYPOINT [ "python", "run.py" ] From e5b7d52bf100498bec00fd781a391e15986cd4d5 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 21:09:06 +0300 Subject: [PATCH 089/140] TARGETPLATFORM condition --- Dockerfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7d62b534..3cd5050f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,8 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip +ARG TARGETPLATFORM + RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -26,7 +28,11 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ - && pip install -r requirements.txt \ + && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + pip install --index-url=https://www.piwheels.org/simple --no-cache-dir -r requirements.txt; \ + else \ + pip install -r requirements.txt; \ + fi \ && pip cache purge \ && apt-get remove -y gcc rustc \ && apt-get autoremove -y \ From 26137faf7c38ac5becbafa76e79f931261e4becd Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 21:09:37 +0300 Subject: [PATCH 090/140] Delete Dockerfile.arm32v7 --- Dockerfile.arm32v7 | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 Dockerfile.arm32v7 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 deleted file mode 100644 index 05126f96..00000000 --- a/Dockerfile.arm32v7 +++ /dev/null @@ -1,39 +0,0 @@ -FROM python:3.8-slim-buster - -ARG BUILDX_QEMU_ENV - -WORKDIR /usr/src/app - -COPY ./requirements.txt ./ - -ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 - -RUN pip install --upgrade pip - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ - gcc \ - libffi-dev \ - rustc \ - zlib1g-dev \ - libjpeg-dev \ - libssl-dev \ - make \ - automake \ - g++ \ - subversion \ - python3-dev \ - && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ - pip install -U cryptography==3.3.2; \ - fi \ - && pip install --index-url=https://www.piwheels.org/simple --no-cache-dir -r requirements.txt \ - && pip cache purge \ - && apt-get remove -y gcc rustc \ - && apt-get autoremove -y \ - && apt-get autoclean -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /usr/share/doc/* - -ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner -ENTRYPOINT [ "python", "run.py" ] From 757fa869512683e28863cd0b9fcc1e6e1e5fc05e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 21:28:50 +0300 Subject: [PATCH 091/140] Update Dockerfile --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3cd5050f..ee38356d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,10 +29,9 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins pip install -U cryptography==3.3.2; \ fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - pip install --index-url=https://www.piwheels.org/simple --no-cache-dir -r requirements.txt; \ - else \ - pip install -r requirements.txt; \ + pip install --index-url=https://www.piwheels.org/simple -U pandas; \ fi \ + && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ && apt-get autoremove -y \ From 414ec7ebba1184a180205b1a81685928a51854f8 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 21:54:22 +0300 Subject: [PATCH 092/140] apt-get install python3-pandas --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ee38356d..e75e647a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins pip install -U cryptography==3.3.2; \ fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - pip install --index-url=https://www.piwheels.org/simple -U pandas; \ + apt-get install python3-pandas; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From 3fb77d8bbeeb8d3b8324d7057b571404295aad09 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:06:22 +0300 Subject: [PATCH 093/140] apt-get -y install python3-pandas --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e75e647a..10517745 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins pip install -U cryptography==3.3.2; \ fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - apt-get install python3-pandas; \ + apt-get -y install python3-pandas; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From cf6760a4d4f7f0561a19df237127dd4dd6d6dd4d Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:27:15 +0300 Subject: [PATCH 094/140] apt & pip install pandas --no-build-isolation --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 10517745..70e00c0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ apt-get -y install python3-pandas; \ + pip install pandas --no-build-isolation; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From 9284c06cc01650226e255475e2539eadfadaed86 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:45:44 +0300 Subject: [PATCH 095/140] apt & sed -i '/pandas/d' requirements.txt --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 70e00c0d..3935ae06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ apt-get -y install python3-pandas; \ - pip install pandas --no-build-isolation; \ + sed -i '/pandas/d' requirements.txt; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From d8603cb5eeaf4929ab2353e03edc49f18f28b1c2 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 14:02:27 +0300 Subject: [PATCH 096/140] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 80b236b1..8e8b88d6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,14 @@ assignees: '' --- +⚠ REPORTS WITHOUT THIS INFORMATION WILL NOT BE ACCEPTED! PLEASE RESPECT OTHER'S TIME! ⚠ + +Please make a proper bug report. The template is put here for a reason, edit it according to your situation. + +It's not cool to just throw lines of log without any context and comments. You have to be specific if you want people to help you. + +👆 + **Describe the bug** A clear and concise description of what the bug is. @@ -23,6 +31,24 @@ A clear and concise description of what you expected to happen. **Desktop (please complete the following information):** - OS: [e.g. Windows] - Python version [e.g. 3.x] + - Miner version + - Other relevant software versions + +**Log** +How to provide a DEBUG log: +1. Set +```py +logger_settings=LoggerSettings( + save=True, + console_level=logging.INFO, + file_level=logging.DEBUG, + less=True, +``` +in your runner script (`run.py`). + +2. Start the miner, wait for the error, then stop the miner and post the contents of the log file (`logs\username.log`) to https://gist.github.com/ and post a link here. + +3. Create another gist with your console output, just in case. Paste a link here as well. **Additional context** Add any other context about the problem here. From ed5db189f4400714954459874f853688f0b5cc0a Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:02:32 +0300 Subject: [PATCH 097/140] apt-get -y install python-pandas --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3935ae06..3beb67be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins pip install -U cryptography==3.3.2; \ fi \ && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - apt-get -y install python3-pandas; \ + apt-get -y install python-pandas; \ sed -i '/pandas/d' requirements.txt; \ fi \ && pip install -r requirements.txt \ From c61aebd5572ba657d9f8c8c65acc66a585dfd720 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 20:08:34 +0300 Subject: [PATCH 098/140] alias python=python3.7 for armv7 apt's version of numpy is for python 3.7. And it doesn't work with python 3.8. So I guess we're gonna have to downgrade to python 3.7 on armv7. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 3beb67be..39231756 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ apt-get -y install python-pandas; \ sed -i '/pandas/d' requirements.txt; \ + alias python=python3.7; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From efd1f412eb73432054814cd95b3f12dc5c0b3ae0 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 20:30:44 +0300 Subject: [PATCH 099/140] testing FROM python:3.11-slim-buster --- Dockerfile | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 39231756..9d0a4fac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-slim-buster +FROM python:3.11-slim-buster ARG BUILDX_QEMU_ENV @@ -10,8 +10,6 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip -ARG TARGETPLATFORM - RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -28,11 +26,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ - && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - apt-get -y install python-pandas; \ - sed -i '/pandas/d' requirements.txt; \ - alias python=python3.7; \ - fi \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ From 9422aebcfa5eb81d88287551579f5b3b52f78bfc Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 21:11:10 +0300 Subject: [PATCH 100/140] testing FROM python:3.11-slim-buster + apt --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 9d0a4fac..4285b58e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,8 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip +ARG TARGETPLATFORM + RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -26,6 +28,10 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ + && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + apt-get -y install python3-pandas; \ + sed -i '/pandas/d' requirements.txt; \ + fi \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ From 9f8d1ed4a659a1ada5aeb70a744770bcd682e4d0 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 21:55:13 +0300 Subject: [PATCH 101/140] alias python=python3.7 --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 4285b58e..b1efd4ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ apt-get -y install python3-pandas; \ sed -i '/pandas/d' requirements.txt; \ + alias python=python3.7; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From 4e42caab1387ca563117271d5ded4f38adb8da0a Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:42:02 +0300 Subject: [PATCH 102/140] ln -sf /usr/bin/python3.7 /usr/local/bin/python --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b1efd4ee..8d973e15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ apt-get -y install python3-pandas; \ sed -i '/pandas/d' requirements.txt; \ - alias python=python3.7; \ + ln -sf /usr/bin/python3.7 /usr/local/bin/python; \ fi \ && pip install -r requirements.txt \ && pip cache purge \ From a1f881b21b84ec38c5033b08dc1d511e057a47a5 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:07:49 +0300 Subject: [PATCH 103/140] back to ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 --- Dockerfile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8d973e15..9d0a4fac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,6 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip -ARG TARGETPLATFORM - RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -28,11 +26,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ - && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - apt-get -y install python3-pandas; \ - sed -i '/pandas/d' requirements.txt; \ - ln -sf /usr/bin/python3.7 /usr/local/bin/python; \ - fi \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ From 708821838b46511fbc52e693c9b03f9f75549675 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:09:31 +0300 Subject: [PATCH 104/140] Create Dockerfile.arm32v7 --- Dockerfile.arm32v7 | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Dockerfile.arm32v7 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 new file mode 100644 index 00000000..0566fd41 --- /dev/null +++ b/Dockerfile.arm32v7 @@ -0,0 +1,39 @@ +FROM python:3.7-slim-buster + +ARG BUILDX_QEMU_ENV + +WORKDIR /usr/src/app + +COPY ./requirements.txt ./ + +ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 + +RUN pip install --upgrade pip + +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ + gcc \ + libffi-dev \ + rustc \ + zlib1g-dev \ + libjpeg-dev \ + libssl-dev \ + make \ + automake \ + g++ \ + subversion \ + python3-dev \ + && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ + pip install -U cryptography==3.3.2; \ + fi \ + && pip install -r requirements.txt \ + && pip cache purge \ + && apt-get remove -y gcc rustc \ + && apt-get autoremove -y \ + && apt-get autoclean -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /usr/share/doc/* + +ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner +ENTRYPOINT [ "python", "run.py" ] From e8e10374d365cb9792d4479e7cfce224947fa001 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:22:31 +0300 Subject: [PATCH 105/140] apt-get -y install python3-pandas --- Dockerfile.arm32v7 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 index 0566fd41..0d24b071 100644 --- a/Dockerfile.arm32v7 +++ b/Dockerfile.arm32v7 @@ -26,6 +26,8 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ + && apt-get -y install python3-pandas \ + && sed -i '/pandas/d' requirements.txt \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ From e40e27b56a80ac9a57f076761cae0a01ba164dde Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:47:49 +0300 Subject: [PATCH 106/140] Update deploy-docker.yml --- .github/workflows/deploy-docker.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 002a669f..e312af8f 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -25,7 +25,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - - name: Build and push + - name: Build and push AMD64, ARM64 id: docker_build uses: docker/build-push-action@v3.1.1 with: @@ -33,7 +33,7 @@ jobs: push: true tags: | rdavidoff/twitch-channel-points-miner-v2:latest - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: linux/amd64,linux/arm64 build-args: BUILDX_QEMU_ENV=true # File size exceeds the maximum allowed 25000 bytes @@ -44,5 +44,20 @@ jobs: # password: ${{ secrets.DOCKER_TOKEN }} # repository: rdavidoff/twitch-channel-points-miner-v2 - - name: Image digest + - name: Image digest AMD64, ARM64 run: echo ${{ steps.docker_build.outputs.digest }} + + - name: Build and push ARMv7 + id: docker_build_armv7 + uses: docker/build-push-action@v3.1.1 + with: + context: . + push: true + tags: | + rdavidoff/twitch-channel-points-miner-v2:latest + platforms: linux/arm/v7 + build-args: BUILDX_QEMU_ENV=true + dockerfile: Dockerfile.arm32v7 + + - name: Image digest ARMv7 + run: echo ${{ steps.docker_build_armv7.outputs.digest }} From e13718073aa70ee5309ecac0c1792db852cc500e Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 18 Nov 2022 00:16:18 +0300 Subject: [PATCH 107/140] Update deploy-docker.yml --- .github/workflows/deploy-docker.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index e312af8f..dcb3dfd2 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -25,7 +25,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - - name: Build and push AMD64, ARM64 + - name: Build and push AMD64, ARM64, ARMv7 id: docker_build uses: docker/build-push-action@v3.1.1 with: @@ -33,7 +33,7 @@ jobs: push: true tags: | rdavidoff/twitch-channel-points-miner-v2:latest - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/arm/v7 build-args: BUILDX_QEMU_ENV=true # File size exceeds the maximum allowed 25000 bytes @@ -44,20 +44,5 @@ jobs: # password: ${{ secrets.DOCKER_TOKEN }} # repository: rdavidoff/twitch-channel-points-miner-v2 - - name: Image digest AMD64, ARM64 + - name: Image digest AMD64, ARM64, ARMv7 run: echo ${{ steps.docker_build.outputs.digest }} - - - name: Build and push ARMv7 - id: docker_build_armv7 - uses: docker/build-push-action@v3.1.1 - with: - context: . - push: true - tags: | - rdavidoff/twitch-channel-points-miner-v2:latest - platforms: linux/arm/v7 - build-args: BUILDX_QEMU_ENV=true - dockerfile: Dockerfile.arm32v7 - - - name: Image digest ARMv7 - run: echo ${{ steps.docker_build_armv7.outputs.digest }} From d59880cb0a8cf2828c7fd997d03db31833813c27 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 18 Nov 2022 00:16:45 +0300 Subject: [PATCH 108/140] Delete Dockerfile.arm32v7 --- Dockerfile.arm32v7 | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 Dockerfile.arm32v7 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 deleted file mode 100644 index 0d24b071..00000000 --- a/Dockerfile.arm32v7 +++ /dev/null @@ -1,41 +0,0 @@ -FROM python:3.7-slim-buster - -ARG BUILDX_QEMU_ENV - -WORKDIR /usr/src/app - -COPY ./requirements.txt ./ - -ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 - -RUN pip install --upgrade pip - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ - gcc \ - libffi-dev \ - rustc \ - zlib1g-dev \ - libjpeg-dev \ - libssl-dev \ - make \ - automake \ - g++ \ - subversion \ - python3-dev \ - && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ - pip install -U cryptography==3.3.2; \ - fi \ - && apt-get -y install python3-pandas \ - && sed -i '/pandas/d' requirements.txt \ - && pip install -r requirements.txt \ - && pip cache purge \ - && apt-get remove -y gcc rustc \ - && apt-get autoremove -y \ - && apt-get autoclean -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /usr/share/doc/* - -ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner -ENTRYPOINT [ "python", "run.py" ] From 64fdea3439fd11dfdd3d0f5a12647f432b37b367 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 18 Nov 2022 00:20:24 +0300 Subject: [PATCH 109/140] ln -sf in the end --- Dockerfile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9d0a4fac..cf8d42e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,8 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip +ARG TARGETPLATFORM + RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -26,6 +28,10 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ + && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + apt-get -y install python3-pandas; \ + sed -i '/pandas/d' requirements.txt; \ + fi \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ @@ -33,7 +39,10 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && apt-get autoclean -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* \ - && rm -rf /usr/share/doc/* + && rm -rf /usr/share/doc/* \ + && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + ln -sf /usr/bin/python3.7 /usr/local/bin/python; \ + fi ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner ENTRYPOINT [ "python", "run.py" ] From b785569ff56d216501e2859fd69c77acad41d285 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 18 Nov 2022 00:58:22 +0300 Subject: [PATCH 110/140] back to ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 --- Dockerfile | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index cf8d42e0..9d0a4fac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,6 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 RUN pip install --upgrade pip -ARG TARGETPLATFORM - RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-install-recommends \ gcc \ @@ -28,10 +26,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && if [ "${BUILDX_QEMU_ENV}" = "true" ] && [ "$(getconf LONG_BIT)" = "32" ]; then \ pip install -U cryptography==3.3.2; \ fi \ - && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - apt-get -y install python3-pandas; \ - sed -i '/pandas/d' requirements.txt; \ - fi \ && pip install -r requirements.txt \ && pip cache purge \ && apt-get remove -y gcc rustc \ @@ -39,10 +33,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --fix-missing --no-ins && apt-get autoclean -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* \ - && rm -rf /usr/share/doc/* \ - && if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ - ln -sf /usr/bin/python3.7 /usr/local/bin/python; \ - fi + && rm -rf /usr/share/doc/* ADD ./TwitchChannelPointsMiner ./TwitchChannelPointsMiner ENTRYPOINT [ "python", "run.py" ] From 750c9cba3f2206484fa84cad0d27746071fc9250 Mon Sep 17 00:00:00 2001 From: bakedbread <110211701+B4kedBr3ad@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:52:41 +0900 Subject: [PATCH 111/140] Rewrite how to run it on Termux --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0eef2045..d1cbe708 100644 --- a/README.md +++ b/README.md @@ -628,32 +628,37 @@ Other useful info can be founded here: You can also follow this [video tutorial](https://www.youtube.com/watch?v=0VkM7NOZkuA). ## Termux -Install the requirements +**0. Install packages on Termux** ``` -pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib` +pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow ``` -**(1 way):** Clone this repository +**1. Clone this repository** + `git clone https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2` -**(2 way):** Download sources from GitHub and put it into your Termux storage +**2. Move directory** -Now you can enter the directory with our miner, type this command: `cd Twitch-Channel-Points-Miner-v2` -Configure your miner on your preferences by typing +**3. Configure your miner on your preferences by typing** + `nano example.py` -When you have configured it now we can rename it (optional): +**4. Rename file name (optional)** + `mv example.py run.py` -We have to also install dependences required to run miner: -`pip install -r requirements.txt` +**5. Install packages** +``` +pip install -r requirements.txt +pip install Twitch-Channel-Points-Miner-v2 +``` -**(3 way):** `pip install Twitch-Channel-Points-Miner-v2` +**6. Run miner!** -Now when we did everything we can run miner: `python run.py` +`python run.py` Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) From 9f6ec4b004cb4974dae007c4b97cd589578a7a1d Mon Sep 17 00:00:00 2001 From: bakedbread <110211701+B4kedBr3ad@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:56:56 +0900 Subject: [PATCH 112/140] add note : if can't install pandas/Cryptography --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d1cbe708..71ea0f08 100644 --- a/README.md +++ b/README.md @@ -662,5 +662,15 @@ pip install Twitch-Channel-Points-Miner-v2 Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) +**Note** +If you can't install pandas, please try it: + +`MATHLIB="m" pip install pandas` + +If you can't install Cryptography, please try it: + +`export RUSTFLAGS=" -C lto=no" && export CARGO_BUILD_TARGET="$(rustc -vV | sed -n 's|host: ||p')" && pip install cryptography` + +⚠️The installation of Pandas and Cryptography takes a long time. ## Disclaimer This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. From 992ad9ef1fb3c8530f2ce6c93527298f35dfb847 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:22:32 +0300 Subject: [PATCH 113/140] termux section corrections --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 71ea0f08..d7b895d5 100644 --- a/README.md +++ b/README.md @@ -628,7 +628,7 @@ Other useful info can be founded here: You can also follow this [video tutorial](https://www.youtube.com/watch?v=0VkM7NOZkuA). ## Termux -**0. Install packages on Termux** +**0. Install packages to Termux** ``` pkg install python git rust libjpeg-turbo libcrypt ndk-sysroot clang zlib LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow @@ -636,9 +636,9 @@ LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade w **1. Clone this repository** -`git clone https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2` +`git clone https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2` -**2. Move directory** +**2. Go to the miner's directory** `cd Twitch-Channel-Points-Miner-v2` @@ -663,14 +663,15 @@ pip install Twitch-Channel-Points-Miner-v2 Read more at [#92](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/92) [#76](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/76) **Note** -If you can't install pandas, please try it: +If you can't install `pandas`, please try: `MATHLIB="m" pip install pandas` -If you can't install Cryptography, please try it: +If you can't install `cryptography`, please try: `export RUSTFLAGS=" -C lto=no" && export CARGO_BUILD_TARGET="$(rustc -vV | sed -n 's|host: ||p')" && pip install cryptography` -⚠️The installation of Pandas and Cryptography takes a long time. +⚠️ Installation of `pandas` and `cryptography` takes a long time. + ## Disclaimer This project comes with no guarantee or warranty. You are responsible for whatever happens from using this project. It is possible to get soft or hard banned by using this project if you are not careful. This is a personal project and is in no way affiliated with Twitch. From afd9c508317fea11f5dca7c4c58db515cd131d1e Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:48:40 +0300 Subject: [PATCH 114/140] show version on start --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 5541b8f0..5368083e 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -121,6 +121,10 @@ def __init__( # Check for the latest version of the script current_version, github_version = check_versions() + + logger.info(f"Twitch Channel Points Miner v2-{current_version} (fork by rdavydov)") + logger.info("https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2") + if github_version == "0.0.0": logger.error( "Unable to detect if you have the latest version of this script" From 39ede95aef8942eaecd971db651b64f7ec5c25b9 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:05:48 +0300 Subject: [PATCH 115/140] Handle error 5023 as a temporary ban from Twitch --- TwitchChannelPointsMiner/classes/TwitchLogin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index a53e5ef6..e87b4d36 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -140,6 +140,13 @@ def login_flow(self): ) use_backup_flow = True break + # https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/issues/46 + elif err_code == 5023: + logger.error( + "Probably an automatic temporary ban from Twitch. Please wait 24 hours till they lift the ban from the account and try again." + ) + use_backup_flow = True + break else: logger.error(f"Unknown error: {login_response}") raise NotImplementedError( From b719fc9d3ffafc74be109eb04f028406f11ec0f1 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:32:34 +0300 Subject: [PATCH 116/140] add Replit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d7b895d5..d1b8fcb4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Currently, we have a lot of PRs requests opened, but the time to test and improv - [Cloning](#by-cloning-the-repository) - [pip](#pip) - [Docker](#docker) + - [Replit](#replit) - [Limits](#limits) 5. 🔧 [Settings](#settings) - [LoggerSettings](#loggersettings) @@ -380,6 +381,13 @@ docker run --name user2 -v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 r About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). +### Replit +Run a new Replit with the master branch: + +[![Run on Repl.it](https://replit.com/badge/github/rdavydov/Twitch-Channel-Points-Miner-v2)](https://replit.com/new/github/rdavydov/Twitch-Channel-Points-Miner-v2) + +Official Replit with description and added keep-alive functionality: https://replit.com/@rdavydov/Twitch-Channel-Points-Miner-v2 + ### Limits > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. From b5160212d95dfbc0916082f7c44babad6d923a66 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:38:49 +0300 Subject: [PATCH 117/140] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d1b8fcb4..ce11f15d 100644 --- a/README.md +++ b/README.md @@ -382,11 +382,11 @@ docker run --name user2 -v $(pwd)/user2.py:/usr/src/app/run.py:ro -p 5002:5000 r About the *Docker* version; the community has always shown great interest in the Docker version of the project. Especially [@SethFalco](https://github.com/SethFalco) ([#79](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/79)), [@KamushekDev](https://github.com/KamushekDev) ([#300](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/300)), [@au-ee](https://github.com/au-ee) ([#223](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/223)) they showed their ideas. I've decided to merge the PR from [@RakSrinaNa](https://github.com/RakSrinaNa) ([#343](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/pull/343)) because is one of the most active user of the project and the PR was the only one with a Continuous Integration (CI). ### Replit -Run a new Replit with the master branch: +Run a new Repl with the master branch: [![Run on Repl.it](https://replit.com/badge/github/rdavydov/Twitch-Channel-Points-Miner-v2)](https://replit.com/new/github/rdavydov/Twitch-Channel-Points-Miner-v2) -Official Replit with description and added keep-alive functionality: https://replit.com/@rdavydov/Twitch-Channel-Points-Miner-v2 +Official Repl with description and added keep-alive functionality: https://replit.com/@rdavydov/Twitch-Channel-Points-Miner-v2 ### Limits > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. From cac767e6b1a1e22a9a91f002b14c55acec27bdae Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:47:23 +0300 Subject: [PATCH 118/140] 1.5.1 --- TwitchChannelPointsMiner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 6d732f0b..f4635469 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.0" +__version__ = "1.5.1" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ From ada696283e63ebbbfc267a2820c161debb4a8ebe Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:41:39 +0300 Subject: [PATCH 119/140] Docker Hub + badges --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index ce11f15d..fd54bdf7 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ GitHub last commit

+

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

+ **Credits** - Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner - ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ @@ -319,6 +328,9 @@ Start mining! `python run.py` 🥳 ### Docker +#### Docker Hub +Official Docker images are on https://hub.docker.com/r/rdavidoff/twitch-channel-points-miner-v2 for `linux/amd64`, `linux/arm64` and `linux/arm/v7`. + The following file is mounted : - run.py : this is your starter script with your configuration From 01567c9580b1d3e0f57c4941b9bb15f43d05fac5 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:48:49 +0300 Subject: [PATCH 120/140] if no username --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 5368083e..b0958fa1 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -81,6 +81,12 @@ def __init__( # Default values for all streamers streamer_settings: StreamerSettings = StreamerSettings(), ): + # Fixes TypeError: 'NoneType' object is not subscriptable + if not username or username == "your-twitch-username": + logger.error("Please edit your runner file (usually run.py) and try again.") + logger.error("No username, exiting...") + sys.exit(0) + # Analytics switch Settings.enable_analytics = enable_analytics From e05decc4e2f5fdb79a42a0398dd84cf3bbeb8580 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:35:21 +0300 Subject: [PATCH 121/140] fix #58 --- .../classes/WebSocketsPool.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index 6061d4c7..22c35cde 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -361,16 +361,20 @@ def on_message(ws, message): ) if event_prediction.result["type"] != "LOSE": - ws.streamers[streamer_index].persistent_annotations( - event_prediction.result["type"], - f"{ws.events_predictions[event_id].title}", - ) + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[streamer_index].persistent_annotations( + event_prediction.result["type"], + f"{ws.events_predictions[event_id].title}", + ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True - ws.streamers[streamer_index].persistent_annotations( - "PREDICTION_MADE", - f"Decision: {event_prediction.bet.decision['choice']} - {event_prediction.title}", - ) + # Analytics switch + if Settings.enable_analytics is True: + ws.streamers[streamer_index].persistent_annotations( + "PREDICTION_MADE", + f"Decision: {event_prediction.bet.decision['choice']} - {event_prediction.title}", + ) except Exception: logger.error( f"Exception raised for topic: {message.topic} and message: {message}", From 249cacaa463104330641a509021f29de8c4ad3be Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:51:03 +0300 Subject: [PATCH 122/140] fix #59 --- TwitchChannelPointsMiner/TwitchChannelPointsMiner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index b0958fa1..93f5c243 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -43,7 +43,8 @@ logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("irc.client").setLevel(logging.ERROR) -logging.getLogger("seleniumwire").setLevel(logging.ERROR) +#logging.getLogger("seleniumwire").setLevel(logging.ERROR) +logging.getLogger("websocket").setLevel(logging.ERROR) logger = logging.getLogger(__name__) From 79d4817a5309331510edb498d37ca373b6aec344 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:52:28 +0300 Subject: [PATCH 123/140] 1.5.2 --- TwitchChannelPointsMiner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index f4635469..b6ed2305 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.1" +__version__ = "1.5.2" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ From 5448d6784dced17543db8ea7966e7dc4aee1a68d Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 24 Nov 2022 10:32:51 +0000 Subject: [PATCH 124/140] Linus Torvalds was here --- fun | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 fun diff --git a/fun b/fun new file mode 100644 index 00000000..e69de29b From a3c8fa0a595b8a5cbdda9bcddeb651c742bdf20c Mon Sep 17 00:00:00 2001 From: Bill Gates Date: Thu, 24 Nov 2022 10:37:14 +0000 Subject: [PATCH 125/140] Bill Gates was here --- fun | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fun diff --git a/fun b/fun deleted file mode 100644 index e69de29b..00000000 From 612577ae49322ec22e7573f8dc7d5537ca8e3c10 Mon Sep 17 00:00:00 2001 From: Bjarne Stroustrup Date: Thu, 24 Nov 2022 10:39:02 +0000 Subject: [PATCH 126/140] Bjarne Stroustrup was here --- fun | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 fun diff --git a/fun b/fun new file mode 100644 index 00000000..e69de29b From 517c46cc3f1d650e7245d92d75b031f289184505 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Nov 2022 10:40:46 +0000 Subject: [PATCH 127/140] Guido van Rossum was here --- fun | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fun diff --git a/fun b/fun deleted file mode 100644 index e69de29b..00000000 From 6d67ca3f1789841040fa633160c563a03cce40a9 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Wed, 30 Nov 2022 23:15:35 +0300 Subject: [PATCH 128/140] temporary Bet divBy0 fix --- TwitchChannelPointsMiner/classes/entities/Bet.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index b2d9c595..b6b3fc88 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -171,13 +171,16 @@ def update_outcomes(self, outcomes): for index in range(0, len(self.outcomes)): self.outcomes[index][OutcomeKeys.PERCENTAGE_USERS] = float_round( (100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) - / self.total_users + #/ self.total_users + / max(self.total_users, 1) ) self.outcomes[index][OutcomeKeys.ODDS] = float_round( - self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + #self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] + self.total_points / max(self.outcomes[index][OutcomeKeys.TOTAL_POINTS], 1) ) self.outcomes[index][OutcomeKeys.ODDS_PERCENTAGE] = float_round( - 100 / self.outcomes[index][OutcomeKeys.ODDS] + #100 / self.outcomes[index][OutcomeKeys.ODDS] + 100 / max(self.outcomes[index][OutcomeKeys.ODDS], 1) ) self.__clear_outcomes() From 5617580e27610e9334b78613c108de2f5e5c1095 Mon Sep 17 00:00:00 2001 From: Roman Davydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 2 Dec 2022 19:14:19 +0300 Subject: [PATCH 129/140] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd54bdf7..8e1a5d93 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ twitch_miner = TwitchChannelPointsMiner( # For example, if in the mine function you don't provide any value for 'make_prediction' but you have set it on TwitchChannelPointsMiner instance, the script will take the value from here. # If you haven't set any value even in the instance the default one will be used -#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server +#twitch_miner.analytics(host="127.0.0.1", port=5000, refresh=5, days_ago=7) # Start the Analytics web-server (replit: host="0.0.0.0") twitch_miner.mine( [ @@ -400,6 +400,14 @@ Run a new Repl with the master branch: Official Repl with description and added keep-alive functionality: https://replit.com/@rdavydov/Twitch-Channel-Points-Miner-v2 +#### Tricks to run 24/7 on Replit for free + +1. Enable Analytics (set `host="0.0.0.0"`) +2. Note down the output URL (in most cases it is `https://Twitch-Channel-Points-Miner-v2..repl.co`) +3. Send an HTTP request to that URL every 5 minutes + +Use a service that can send HTTP requests at regular intervals, such as Uptimerobot. + ### Limits > Twitch has a limit - you can't watch more than two channels at one time. We take the first two streamers from the list as they have the highest priority. From af2057e61f92b224811f5efd8998abbc68882ae3 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 2 Dec 2022 19:56:40 +0300 Subject: [PATCH 130/140] Limited support for predictions with > 2 outcomes --- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/classes/Twitch.py | 5 +- .../classes/entities/Bet.py | 56 +++++++++++-------- TwitchChannelPointsMiner/utils.py | 5 +- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index b6ed2305..3a76474d 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.2" +__version__ = "1.5.3" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 17d1873e..9424776a 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -542,7 +542,7 @@ def load_channel_points_context(self, streamer): def make_predictions(self, event): decision = event.bet.calculate(event.streamer.channel_points) - selector_index = 0 if decision["choice"] == "A" else 1 + #selector_index = 0 if decision["choice"] == "A" else 1 logger.info( f"Going to complete bet for {event}", @@ -571,7 +571,8 @@ def make_predictions(self, event): else: if decision["amount"] >= 10: logger.info( - f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + #f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(decision['choice'])}", extra={ "emoji": ":four_leaf_clover:", "event": Events.BET_GENERAL, diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index b6b3fc88..b6234d82 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -4,7 +4,8 @@ from millify import millify -from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round +#from TwitchChannelPointsMiner.utils import char_decision_as_index, float_round +from TwitchChannelPointsMiner.utils import float_round class Strategy(Enum): @@ -154,33 +155,32 @@ def update_outcomes(self, outcomes): 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] - + self.outcomes[1][OutcomeKeys.TOTAL_USERS] - ) - self.total_points = ( - self.outcomes[0][OutcomeKeys.TOTAL_POINTS] - + self.outcomes[1][OutcomeKeys.TOTAL_POINTS] - ) + # Inefficient, but otherwise outcomekeys are represented wrong + self.total_points = 0 + self.total_users = 0 + for index in range(0, len(self.outcomes)): + self.total_users += self.outcomes[index][OutcomeKeys.TOTAL_USERS] + self.total_points += self.outcomes[index][OutcomeKeys.TOTAL_POINTS] if ( self.total_users > 0 - and self.outcomes[0][OutcomeKeys.TOTAL_POINTS] > 0 - and self.outcomes[1][OutcomeKeys.TOTAL_POINTS] > 0 + and self.total_points > 0 ): for index in range(0, len(self.outcomes)): self.outcomes[index][OutcomeKeys.PERCENTAGE_USERS] = float_round( - (100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) - #/ self.total_users - / max(self.total_users, 1) + (100 * self.outcomes[index][OutcomeKeys.TOTAL_USERS]) / self.total_users ) self.outcomes[index][OutcomeKeys.ODDS] = float_round( - #self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] - self.total_points / max(self.outcomes[index][OutcomeKeys.TOTAL_POINTS], 1) + #self.total_points / max(self.outcomes[index][OutcomeKeys.TOTAL_POINTS], 1) + 0 + if self.outcomes[index][OutcomeKeys.TOTAL_POINTS] == 0 + else self.total_points / self.outcomes[index][OutcomeKeys.TOTAL_POINTS] ) self.outcomes[index][OutcomeKeys.ODDS_PERCENTAGE] = float_round( - #100 / self.outcomes[index][OutcomeKeys.ODDS] - 100 / max(self.outcomes[index][OutcomeKeys.ODDS], 1) + #100 / max(self.outcomes[index][OutcomeKeys.ODDS], 1) + 0 + if self.outcomes[index][OutcomeKeys.ODDS] == 0 + else 100 / self.outcomes[index][OutcomeKeys.ODDS] ) self.__clear_outcomes() @@ -189,7 +189,8 @@ def __repr__(self): return f"Bet(total_users={millify(self.total_users)}, total_points={millify(self.total_points)}), decision={self.decision})\n\t\tOutcome A({self.get_outcome(0)})\n\t\tOutcome B({self.get_outcome(1)})" def get_decision(self, parsed=False): - decision = self.outcomes[0 if self.decision["choice"] == "A" else 1] + #decision = self.outcomes[0 if self.decision["choice"] == "A" else 1] + decision = self.outcomes[self.decision["choice"]] return decision if parsed is False else Bet.__parse_outcome(decision) @staticmethod @@ -224,8 +225,15 @@ def __clear_outcomes(self): if key not in self.outcomes[index]: self.outcomes[index][key] = 0 - def __return_choice(self, key) -> str: - return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B" + '''def __return_choice(self, key) -> str: + return "A" if self.outcomes[0][key] > self.outcomes[1][key] else "B"''' + + def __return_choice(self, key) -> int: + largest=0 + for index in range(0, len(self.outcomes)): + if self.outcomes[index][key] > self.outcomes[largest][key]: + largest = index + return largest def skip(self) -> bool: if self.settings.filter_condition is not None: @@ -244,7 +252,8 @@ def skip(self) -> bool: self.outcomes[0][fixed_key] + self.outcomes[1][fixed_key] ) else: - outcome_index = char_decision_as_index(self.decision["choice"]) + #outcome_index = char_decision_as_index(self.decision["choice"]) + outcome_index = self.decision["choice"] compared_value = self.outcomes[outcome_index][fixed_key] # Check if condition is satisfied @@ -286,7 +295,8 @@ def calculate(self, balance: int) -> dict: ) if self.decision["choice"] is not None: - index = char_decision_as_index(self.decision["choice"]) + #index = char_decision_as_index(self.decision["choice"]) + index = self.decision["choice"] self.decision["id"] = self.outcomes[index]["id"] self.decision["amount"] = min( int(balance * (self.settings.percentage / 100)), diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index fd88c5a5..a9d71eb2 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -138,9 +138,8 @@ def set_default_settings(settings, defaults): ) -def char_decision_as_index(char): - return 0 if char == "A" else 1 - +'''def char_decision_as_index(char): + return 0 if char == "A" else 1''' def internet_connection_available(host="8.8.8.8", port=53, timeout=3): try: From 679d36d5f398ff07c845e5fd265a673fee3c04e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 15:19:44 +0000 Subject: [PATCH 131/140] Bump docker/build-push-action from 3.1.1 to 3.2.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v3.1.1...v3.2.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index dcb3dfd2..0aab7563 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -27,7 +27,7 @@ jobs: - name: Build and push AMD64, ARM64, ARMv7 id: docker_build - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.2.0 with: context: . push: true From 24a86f9350ad5eba6c0e285099a668d5cc65efc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 15:19:53 +0000 Subject: [PATCH 132/140] Bump docker/setup-buildx-action from 2.0.0 to 2.2.1 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.0.0 to 2.2.1. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.0.0...v2.2.1) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index dcb3dfd2..389379d9 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -17,7 +17,7 @@ jobs: uses: docker/setup-qemu-action@v2.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.0.0 + uses: docker/setup-buildx-action@v2.2.1 - name: Login to DockerHub uses: docker/login-action@v2.0.0 From 4ee4e67ea0c247839fc2c3123433b1f75fcee06f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 17:33:51 +0000 Subject: [PATCH 133/140] Bump docker/login-action from 2.0.0 to 2.1.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 389379d9..744ead9a 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -20,7 +20,7 @@ jobs: uses: docker/setup-buildx-action@v2.2.1 - name: Login to DockerHub - uses: docker/login-action@v2.0.0 + uses: docker/login-action@v2.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} From 4cfea477ca74c82ea8870a612b74ae41de20aff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 17:33:51 +0000 Subject: [PATCH 134/140] Bump docker/setup-qemu-action from 2.0.0 to 2.1.0 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 389379d9..04937b70 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v2.0.0 + uses: docker/setup-qemu-action@v2.1.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2.2.1 From ea2a47cd94e3950fa3e4e409e547af064f00bd7f Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:08:26 +0300 Subject: [PATCH 135/140] new android app ver, show err 5023 --- TwitchChannelPointsMiner/classes/TwitchLogin.py | 12 ++++++------ TwitchChannelPointsMiner/constants.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index e87b4d36..d439e036 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -141,12 +141,12 @@ def login_flow(self): use_backup_flow = True break # https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2/issues/46 - elif err_code == 5023: - logger.error( - "Probably an automatic temporary ban from Twitch. Please wait 24 hours till they lift the ban from the account and try again." - ) - use_backup_flow = True - break +# elif err_code == 5023: +# logger.error( +# "Probably an automatic temporary ban from Twitch. Please wait 24 hours till they lift the ban from the account and try again." +# ) +# use_backup_flow = True +# break else: logger.error(f"Unknown error: {login_response}") raise NotImplementedError( diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index e81966ca..4da3a025 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -20,7 +20,8 @@ "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", }, "Android": { - "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" + #"App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" + "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G977N Build/LMY48Z) tv.twitch.android.app/14.3.2/1403020" } } From 163d7de8f5bbc005afc95d2d3847db11fc12c2e8 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Fri, 16 Dec 2022 19:52:03 +0300 Subject: [PATCH 136/140] 1.5.4 temporary login fix --- TwitchChannelPointsMiner/__init__.py | 2 +- .../classes/TwitchLogin.py | 60 ++++++++++++++----- requirements.txt | 1 + setup.py | 3 +- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 3a76474d..1c7a048c 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.3" +__version__ = "1.5.4" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/TwitchLogin.py b/TwitchChannelPointsMiner/classes/TwitchLogin.py index d439e036..f11fe808 100644 --- a/TwitchChannelPointsMiner/classes/TwitchLogin.py +++ b/TwitchChannelPointsMiner/classes/TwitchLogin.py @@ -8,6 +8,8 @@ import os import pickle +import browser_cookie3 + import requests from TwitchChannelPointsMiner.classes.Exceptions import ( @@ -43,7 +45,8 @@ class TwitchLogin(object): "password", "user_id", "email", - "cookies" + "cookies", + "shared_cookies" ] def __init__(self, client_id, device_id, username, user_agent, password=None): @@ -61,6 +64,7 @@ def __init__(self, client_id, device_id, username, user_agent, password=None): self.email = None self.cookies = [] + self.shared_cookies = [] def login_flow(self): logger.info("You'll have to login to Twitch!") @@ -71,8 +75,8 @@ def login_flow(self): "remember_me": True, } # login-fix - use_backup_flow = False - #use_backup_flow = True + #use_backup_flow = False + use_backup_flow = True for attempt in range(0, 25): password = ( @@ -133,8 +137,8 @@ def login_flow(self): # If the user didn't load the password from run.py we can just ask for it again. break # login-fix - elif err_code == 1000: - #elif err_code in [1000, 5022]: + #elif err_code == 1000: + elif err_code in [1000, 5022, 5023, 5024]: logger.info( "Console login unavailable (CAPTCHA solving required)." ) @@ -235,8 +239,33 @@ def login_flow_backup(self, password = None): return False return self.get_cookie_value("auth-token")""" - logger.error("Backup login flow is not available. Use a VPN or wait a while to avoid the CAPTCHA.") - return False + + #logger.error("Backup login flow is not available. Use a VPN or wait a while to avoid the CAPTCHA.") + #return False + + """Backup OAuth login flow in case manual captcha solving is required""" + browser = input( + "What browser do you use? Chrome (1), Firefox (2), Other (3): " + ).strip() + if browser not in ("1", "2"): + logger.info("Your browser is unsupported, sorry.") + return None + + input( + "Please login inside your browser of choice (NOT incognito mode) and press Enter..." + ) + logger.info("Loading cookies saved on your computer...") + twitch_domain = ".twitch.tv" + if browser == "1": # chrome + cookie_jar = browser_cookie3.chrome(domain_name=twitch_domain) + else: + cookie_jar = browser_cookie3.firefox(domain_name=twitch_domain) + #logger.info(f"cookie_jar: {cookie_jar}") + cookies_dict = requests.utils.dict_from_cookiejar(cookie_jar) + #logger.info(f"cookies_dict: {cookies_dict}") + self.username = cookies_dict.get("login") + self.shared_cookies = cookies_dict + return cookies_dict.get("auth-token") def check_login(self): if self.login_check_result: @@ -248,17 +277,20 @@ def check_login(self): return self.login_check_result def save_cookies(self, cookies_file): - #pickle.dump(self.cookies, open(cookies_file, "wb")) - # ^ only this line was needed with Selenium ^ - cookies_dict = self.session.cookies.get_dict() - cookies_dict["auth-token"] = self.token - if "persistent" not in cookies_dict: # saving user id cookies - cookies_dict["persistent"] = self.user_id + #cookies_dict = self.session.cookies.get_dict() + #print(f"cookies_dict2pickle: {cookies_dict}") + #cookies_dict["auth-token"] = self.token + #if "persistent" not in cookies_dict: # saving user id cookies + # cookies_dict["persistent"] = self.user_id + # old way saves only 'auth-token' and 'persistent' self.cookies = [] + cookies_dict = self.shared_cookies + #print(f"cookies_dict2pickle: {cookies_dict}") for cookie_name, value in cookies_dict.items(): self.cookies.append({"name": cookie_name, "value": value}) - pickle.dump(self.cookies, open(cookies_file, "wb")) + #print(f"cookies2pickle: {self.cookies}") + pickle.dump(self.cookies, open(cookies_file, "wb")) def get_cookie_value(self, key): for cookie in self.cookies: diff --git a/requirements.txt b/requirements.txt index 5a011c2f..23e6dbb7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ colorama flask irc pandas +browser_cookie3 diff --git a/setup.py b/setup.py index 30afcecd..18868643 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,8 @@ def read(fname): "colorama", "flask", "irc", - "pandas" + "pandas", + "browser_cookie3" ], long_description=read("README.md"), long_description_content_type="text/markdown", From e7a107002c534d97166835ae7be1548af49a4421 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 17 Dec 2022 17:08:12 +0300 Subject: [PATCH 137/140] debug config --- .vscode/launch.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..be31cde6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: run.py", + "type": "python", + "request": "launch", + "program": "${cwd}/run.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file From 6369fbe00223bd6fda3a028aeaea12c04b07ee25 Mon Sep 17 00:00:00 2001 From: rdavydov <15850461+rdavydov@users.noreply.github.com> Date: Sat, 17 Dec 2022 18:28:34 +0300 Subject: [PATCH 138/140] fix #85 --- .../TwitchChannelPointsMiner.py | 46 ++-- TwitchChannelPointsMiner/__init__.py | 2 +- TwitchChannelPointsMiner/classes/Twitch.py | 250 ++++++++++++++---- TwitchChannelPointsMiner/constants.py | 11 +- TwitchChannelPointsMiner/utils.py | 15 +- requirements.txt | 3 + setup.py | 5 +- 7 files changed, 255 insertions(+), 77 deletions(-) diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index 93f5c243..282126a1 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -43,7 +43,7 @@ logging.getLogger("requests").setLevel(logging.ERROR) logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("irc.client").setLevel(logging.ERROR) -#logging.getLogger("seleniumwire").setLevel(logging.ERROR) +logging.getLogger("seleniumwire").setLevel(logging.ERROR) logging.getLogger("websocket").setLevel(logging.ERROR) logger = logging.getLogger(__name__) @@ -84,7 +84,8 @@ def __init__( ): # Fixes TypeError: 'NoneType' object is not subscriptable if not username or username == "your-twitch-username": - logger.error("Please edit your runner file (usually run.py) and try again.") + logger.error( + "Please edit your runner file (usually run.py) and try again.") logger.error("No username, exiting...") sys.exit(0) @@ -92,7 +93,8 @@ def __init__( Settings.enable_analytics = enable_analytics if enable_analytics is True: - Settings.analytics_path = os.path.join(Path().absolute(), "analytics", username) + Settings.analytics_path = os.path.join( + Path().absolute(), "analytics", username) Path(Settings.analytics_path).mkdir(parents=True, exist_ok=True) self.username = username @@ -105,7 +107,8 @@ def __init__( streamer_settings.bet.default() Settings.streamer_settings = streamer_settings - user_agent = get_user_agent("FIREFOX") + # user_agent = get_user_agent("FIREFOX") + user_agent = get_user_agent("CHROME") self.twitch = Twitch(self.username, user_agent, password) self.claim_drops_startup = claim_drops_startup @@ -129,15 +132,18 @@ def __init__( # Check for the latest version of the script current_version, github_version = check_versions() - logger.info(f"Twitch Channel Points Miner v2-{current_version} (fork by rdavydov)") - logger.info("https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2") + logger.info( + f"Twitch Channel Points Miner v2-{current_version} (fork by rdavydov)") + logger.info( + "https://github.com/rdavydov/Twitch-Channel-Points-Miner-v2") if github_version == "0.0.0": logger.error( "Unable to detect if you have the latest version of this script" ) elif current_version != github_version: - logger.info(f"You are running the version {current_version} of this script") + logger.info( + f"You are running the version {current_version} of this script") logger.info(f"The latest version on GitHub is: {github_version}") for sign in [signal.SIGINT, signal.SIGSEGV, signal.SIGTERM]: @@ -153,7 +159,7 @@ def analytics( # Analytics switch if Settings.enable_analytics is True: from TwitchChannelPointsMiner.classes.AnalyticsServer import AnalyticsServer - + http_server = AnalyticsServer( host=host, port=port, refresh=refresh, days_ago=days_ago ) @@ -161,7 +167,8 @@ def analytics( http_server.name = "Analytics Thread" http_server.start() else: - logger.error("Can't start analytics(), please set enable_analytics=True") + logger.error( + "Can't start analytics(), please set enable_analytics=True") def mine( self, @@ -207,7 +214,8 @@ def run( streamers_dict[username] = streamer if followers is True: - followers_array = self.twitch.get_followers(order=followers_order) + followers_array = self.twitch.get_followers( + order=followers_order) logger.info( f"Load {len(followers_array)} followers from your profile!", extra={"emoji": ":clipboard:"}, @@ -230,7 +238,8 @@ def run( if isinstance(streamers_dict[username], Streamer) is True else Streamer(username) ) - streamer.channel_id = self.twitch.get_channel_id(username) + streamer.channel_id = self.twitch.get_channel_id( + username) streamer.settings = set_default_settings( streamer.settings, Settings.streamer_settings ) @@ -272,7 +281,8 @@ def run( # If we have at least one streamer with settings = claim_drops True # Spawn a thread for sync inventory and dashboard if ( - at_least_one_value_in_settings_is(self.streamers, "claim_drops", True) + at_least_one_value_in_settings_is( + self.streamers, "claim_drops", True) is True ): self.sync_campaigns_thread = threading.Thread( @@ -298,8 +308,8 @@ def run( # Subscribe to community-points-user. Get update for points spent or gains user_id = self.twitch.twitch_login.get_user_id() - #print(f"!!!!!!!!!!!!!! USER_ID: {user_id}") - + # print(f"!!!!!!!!!!!!!! USER_ID: {user_id}") + # Fixes 'ERR_BADAUTH' if not user_id: logger.error("No user_id, exiting...") @@ -331,7 +341,8 @@ def run( if streamer.settings.make_predictions is True: self.ws_pool.submit( - PubsubTopic("predictions-channel-v1", streamer=streamer) + PubsubTopic("predictions-channel-v1", + streamer=streamer) ) refresh_context = time.time() @@ -348,7 +359,8 @@ def run( logger.info( f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..." ) - WebSocketsPool.handle_reconnection(self.ws_pool.ws[index]) + WebSocketsPool.handle_reconnection( + self.ws_pool.ws[index]) if ((time.time() - refresh_context) // 60) >= 30: refresh_context = time.time() @@ -445,4 +457,4 @@ def __print_report(self): logger.info( f"{self.streamers[streamer_index].print_history()}", extra={"emoji": ":moneybag:"}, - ) \ No newline at end of file + ) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index 1c7a048c..11d7d3c0 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.5.4" +__version__ = "1.6.0" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index 9424776a..9f7025c8 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -16,10 +16,17 @@ from secrets import choice, token_hex import json +import pickle +from base64 import urlsafe_b64decode + +from hashlib import sha256 from base64 import urlsafe_b64decode import requests +from undetected_chromedriver import ChromeOptions +import seleniumwire.undetected_chromedriver.v2 as uc + from TwitchChannelPointsMiner.classes.entities.Campaign import Campaign from TwitchChannelPointsMiner.classes.entities.Drop import Drop from TwitchChannelPointsMiner.classes.Exceptions import ( @@ -47,6 +54,71 @@ logger = logging.getLogger(__name__) +HEX_CHARS = '0123456789abcdef' +difficulty_1 = 10 +subchallengeCount = 2 +platformInputs = "tp-v2-input" + + +def get_time_now() -> int: + time_now = f'{time.time()}' + return int(time_now.replace('.', '')[:13]) + + +def random_string(k=32): + return ''.join(random.choices(HEX_CHARS, k=k)) + + +def string_to_sha256(string_to: str): + return sha256(string_to.encode()).hexdigest() + + +def get_hash_difficulty(hash_string): + return 4503599627370496 / (int(f'0x{hash_string[:13]}', 16) + 1) + + +def prof_work(config, _id, work_time): + f_list = [] + difficulty = config['difficulty'] / config['subchallengeCount'] + start_string = string_to_sha256( + platformInputs + ',\x20' + str(work_time) + ',\x20' + _id) + for _ in range(config['subchallengeCount']): + d = 1 + while True: + start_string2 = string_to_sha256(str(d) + ',\x20' + start_string) + if (get_hash_difficulty(start_string2) >= difficulty): + f_list.append(d) + start_string = start_string2 + break + d += 1 + return { + 'answers': f_list, + 'finalHash': start_string + } + + +def get_kpsdk_cd(): + config = { + "platformInputs": "tp-v2-input", + "difficulty": 10, + "subchallengeCount": 2 + } + t0 = time.perf_counter() + _id = random_string() + work_time = get_time_now() - 527 + prof_of_work = prof_work(config, _id, work_time) + t1 = time.perf_counter_ns() + + duration = round(1000 * (t1 - t0)) / 1000 + return { + 'answers': prof_of_work['answers'], + 'rst': time.perf_counter_ns(), + 'st': time.perf_counter_ns(), + 'd': duration, + 'id': _id, + 'workTime': work_time, + } + class Twitch(object): __slots__ = [ @@ -127,22 +199,26 @@ def update_stream(self, streamer): def get_spade_url(self, streamer): try: # fixes AttributeError: 'NoneType' object has no attribute 'group' - #headers = {"User-Agent": self.user_agent} + # headers = {"User-Agent": self.user_agent} from TwitchChannelPointsMiner.constants import USER_AGENTS - headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + # headers = {"User-Agent": USER_AGENTS["Linux"]["FIREFOX"]} + headers = {"User-Agent": USER_AGENTS["Windows"]["CHROME"]} - main_page_request = requests.get(streamer.streamer_url, headers=headers) + main_page_request = requests.get( + streamer.streamer_url, headers=headers) response = main_page_request.text - #logger.info(response) + # logger.info(response) regex_settings = "(https://static.twitchcdn.net/config/settings.*?js)" settings_url = re.search(regex_settings, response).group(1) settings_request = requests.get(settings_url, headers=headers) response = settings_request.text regex_spade = '"spade_url":"(.*?)"' - streamer.stream.spade_url = re.search(regex_spade, response).group(1) + streamer.stream.spade_url = re.search( + regex_spade, response).group(1) except requests.exceptions.RequestException as e: - logger.error(f"Something went wrong during extraction of 'spade_url': {e}") + logger.error( + f"Something went wrong during extraction of 'spade_url': {e}") def get_broadcast_id(self, streamer): json_data = copy.deepcopy(GQLOperations.WithIsStreamLiveQuery) @@ -156,7 +232,8 @@ def get_broadcast_id(self, streamer): raise StreamerIsOfflineException def get_stream_info(self, streamer): - json_data = copy.deepcopy(GQLOperations.VideoPlayerStreamInfoOverlayChannel) + json_data = copy.deepcopy( + GQLOperations.VideoPlayerStreamInfoOverlayChannel) json_data["variables"] = {"channel": streamer.username} response = self.post_gql_request(json_data) if response != {}: @@ -228,7 +305,8 @@ def update_raid(self, streamer, raid): logger.info( f"Joining raid from {streamer} to {raid.target_login}!", - extra={"emoji": ":performing_arts:", "event": Events.JOIN_RAID}, + extra={"emoji": ":performing_arts:", + "event": Events.JOIN_RAID}, ) def viewer_is_mod(self, streamer): @@ -294,45 +372,109 @@ def post_integrity(self): ): return self.integrity try: - response = requests.post( + # is_bad_bot becomes true when I use headless mode. + HEADLESS = False + + options = uc.ChromeOptions() + if HEADLESS is True: + options.add_argument('--headless') + options.add_argument('--log-level=3') + options.add_argument('--disable-web-security') + options.add_argument('--allow-running-insecure-content') + options.add_argument('--lang=en') + options.add_argument('--no-sandbox') + options.add_argument('--disable-gpu') + # options.add_argument("--user-agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"") + # options.add_argument("--window-size=1920,1080") + # options.set_capability("detach", True) + + driver = uc.Chrome( + options=options, use_subprocess=True # , executable_path=EXECUTABLE_PATH + ) + driver.minimize_window() + driver.get('https://www.twitch.tv/robots.txt') + cookies = pickle.load(open(self.cookies_file, "rb")) + for cookie in cookies: + driver.add_cookie(cookie) + driver.get('https://www.twitch.tv/settings/profile') + + # Print request headers + # for request in driver.requests: + # logger.info(request.url) # <--------------- Request url + # logger.info(request.headers) # <----------- Request headers + # logger.info(request.response.headers) # <-- Response headers + + # Set correct user agent + selenium_user_agent = driver.execute_script( + "return navigator.userAgent;") + + self.user_agent = selenium_user_agent + + session = requests.Session() + kpsdk_cd = json.dumps(get_kpsdk_cd()) + + for cookie in driver.get_cookies(): + session.cookies.set(cookie['name'], cookie['value'], + domain=cookie['domain']) + if cookie["name"] == "KP_UIDz": + if cookie["value"] is not None: + kpsdk_ct = cookie["value"] + else: + logger.error("Can't extract kpsdk_ct") + + headers = { + 'Accept': '*/*', + 'Accept-Language': 'en-US,en;q=0.9,pt;q=0.8', + "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", + "Client-Id": CLIENT_ID, + "Client-Session-Id": self.client_session, + "Client-Version": self.update_client_version(), + 'Origin': 'https://www.twitch.tv', + 'Referer': 'https://www.twitch.tv/', + "User-Agent": self.user_agent, + # "User-Agent": selenium_user_agent, + "X-Device-Id": self.device_id, + 'x-kpsdk-cd': kpsdk_cd, + 'x-kpsdk-ct': kpsdk_ct + } + + response = session.post( GQLOperations.integrity_url, json={}, - headers={ - "Authorization": f"OAuth {self.twitch_login.get_auth_token()}", - "Client-Id": CLIENT_ID, - "Client-Session-Id": self.client_session, - "Client-Version": self.update_client_version(), - "User-Agent": self.user_agent, - "X-Device-Id": self.device_id, - }, - ) - logger.debug( - f"Data: [], Status code: {response.status_code}, Content: {response.text}" + headers=headers ) - self.integrity = response.json().get("token", None) - #logger.info(f"integrity: {self.integrity}") - + + driver.close() + driver.quit() + + integrity_json = response.json() + + self.integrity = integrity_json.get("token", None) + # logger.info(f"integrity: {self.integrity}") + if self.isBadBot(self.integrity) is True: - logger.info("Uh-oh, Twitch has detected this miner as a \"Bad Bot\". Don't worry.") - - self.integrity_expire = response.json().get("expiration", 0) - #logger.info(f"integrity_expire: {self.integrity_expire}") + logger.error( + "Uh-oh, Twitch has detected this miner as a \"Bad Bot\"") + + self.integrity_expire = integrity_json.get("expiration", 0) + # logger.info(f"integrity_expire: {self.integrity_expire}") return self.integrity except requests.exceptions.RequestException as e: logger.error(f"Error with post_integrity: {e}") return self.integrity # verify the integrity token's contents for the "is_bad_bot" flag - def isBadBot(self, integrity): + def isBadBot(self, integrity): stripped_token: str = self.integrity.split('.')[2] + "==" - messy_json: str = urlsafe_b64decode(stripped_token.encode()).decode(errors="ignore") + messy_json: str = urlsafe_b64decode( + stripped_token.encode()).decode(errors="ignore") match = re.search(r'(.+)(?<="}).+$', messy_json) if match is None: - #raise MinerException("Unable to parse the integrity token") + # raise MinerException("Unable to parse the integrity token") logger.info("Unable to parse the integrity token. Don't worry.") return decoded_header = json.loads(match.group(1)) - #logger.info(f"decoded_header: {decoded_header}") + # logger.info(f"decoded_header: {decoded_header}") if decoded_header.get("is_bad_bot", "false") != "false": return True else: @@ -383,11 +525,13 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): streamers_watching += streamers_index[:2] elif ( - prior in [Priority.POINTS_ASCENDING, Priority.POINTS_DESCEDING] + prior in [Priority.POINTS_ASCENDING, + Priority.POINTS_DESCEDING] and len(streamers_watching) < 2 ): items = [ - {"points": streamers[index].channel_points, "index": index} + {"points": streamers[index].channel_points, + "index": index} for index in streamers_index ] items = sorted( @@ -397,7 +541,8 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): True if prior == Priority.POINTS_DESCEDING else False ), ) - streamers_watching += [item["index"] for item in items][:2] + streamers_watching += [item["index"] + for item in items][:2] elif prior == Priority.STREAK and len(streamers_watching) < 2: """ @@ -413,7 +558,8 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): and ( streamers[index].offline_at == 0 or ( - (time.time() - streamers[index].offline_at) + (time.time() - + streamers[index].offline_at) // 60 ) > 30 @@ -439,7 +585,8 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): ] streamers_with_multiplier = sorted( streamers_with_multiplier, - key=lambda x: streamers[x].total_points_multiplier(), + key=lambda x: streamers[x].total_points_multiplier( + ), reverse=True, ) streamers_watching += streamers_with_multiplier[:2] @@ -508,10 +655,12 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): ) except requests.exceptions.ConnectionError as e: - logger.error(f"Error while trying to send minute watched: {e}") + logger.error( + f"Error while trying to send minute watched: {e}") self.__check_connection_handler(chunk_size) except requests.exceptions.Timeout as e: - logger.error(f"Error while trying to send minute watched: {e}") + logger.error( + f"Error while trying to send minute watched: {e}") self.__chuncked_sleep( next_iteration - time.time(), chunk_size=chunk_size @@ -520,7 +669,8 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3): if streamers_watching == []: self.__chuncked_sleep(60, chunk_size=chunk_size) except Exception: - logger.error("Exception raised in send minute watched", exc_info=True) + logger.error( + "Exception raised in send minute watched", exc_info=True) # === CHANNEL POINTS / PREDICTION === # # Load the amount of current points for a channel, check if a bonus is available @@ -538,11 +688,12 @@ def load_channel_points_context(self, streamer): streamer.activeMultipliers = community_points["activeMultipliers"] if community_points["availableClaim"] is not None: - self.claim_bonus(streamer, community_points["availableClaim"]["id"]) + self.claim_bonus( + streamer, community_points["availableClaim"]["id"]) def make_predictions(self, event): decision = event.bet.calculate(event.streamer.channel_points) - #selector_index = 0 if decision["choice"] == "A" else 1 + # selector_index = 0 if decision["choice"] == "A" else 1 logger.info( f"Going to complete bet for {event}", @@ -571,7 +722,7 @@ def make_predictions(self, event): else: if decision["amount"] >= 10: logger.info( - #f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + # f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(decision['choice'])}", extra={ "emoji": ":four_leaf_clover:", @@ -635,7 +786,8 @@ def claim_bonus(self, streamer, claim_id): # === CAMPAIGNS / DROPS / INVENTORY === # def __get_campaign_ids_from_streamer(self, streamer): - json_data = copy.deepcopy(GQLOperations.DropsHighlightService_AvailableDrops) + json_data = copy.deepcopy( + GQLOperations.DropsHighlightService_AvailableDrops) json_data["variables"] = {"channelID": streamer.channel_id} response = self.post_gql_request(json_data) try: @@ -663,7 +815,8 @@ 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)) + campaigns = list( + filter(lambda x: x["status"] == status.upper(), campaigns)) return campaigns def __get_campaigns_details(self, campaigns): @@ -672,7 +825,8 @@ def __get_campaigns_details(self, campaigns): for chunk in chunks: json_data = [] for campaign in chunk: - json_data.append(copy.deepcopy(GQLOperations.DropCampaignDetails)) + json_data.append(copy.deepcopy( + GQLOperations.DropCampaignDetails)) json_data[-1]["variables"] = { "dropID": campaign["id"], "channelLogin": f"{self.twitch_login.get_user_id()}", @@ -703,7 +857,8 @@ def __sync_campaigns(self, campaigns): campaigns[i].sync_drops( progress["timeBasedDrops"], self.claim_drop ) - campaigns[i].clear_drops() # Remove all the claimed drops + # Remove all the claimed drops + campaigns[i].clear_drops() break return campaigns @@ -713,7 +868,8 @@ def claim_drop(self, drop): ) json_data = copy.deepcopy(GQLOperations.DropsPage_ClaimDropRewards) - json_data["variables"] = {"input": {"dropInstanceID": drop.drop_instance_id}} + json_data["variables"] = { + "input": {"dropInstanceID": drop.drop_instance_id}} response = self.post_gql_request(json_data) try: # response["data"]["claimDropRewards"] can be null and respose["data"]["errors"] != [] diff --git a/TwitchChannelPointsMiner/constants.py b/TwitchChannelPointsMiner/constants.py index 4da3a025..0adfbac1 100644 --- a/TwitchChannelPointsMiner/constants.py +++ b/TwitchChannelPointsMiner/constants.py @@ -3,16 +3,15 @@ IRC = "irc.chat.twitch.tv" IRC_PORT = 6667 WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" -#CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" # Browser -CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App -#CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App +CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" # Browser +# CLIENT_ID = "kd1unb4b3q4t58fwlpcbzcbnm76a8fp" # Android App +# CLIENT_ID = "851cqzxpb9bqu9z6galo155du" # iOS App DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" CLIENT_VERSION = "32d439b2-bd5b-4e35-b82a-fae10b04da70" USER_AGENTS = { "Windows": { - #"CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", - 'CHROME': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", + 'CHROME': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", "FIREFOX": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", }, "Linux": { @@ -20,7 +19,7 @@ "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", }, "Android": { - #"App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" + # "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G975N Build/N2G48C) tv.twitch.android.app/13.4.1/1304010" "App": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G977N Build/LMY48Z) tv.twitch.android.app/14.3.2/1403020" } } diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index a9d71eb2..6a4bd9e1 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -32,7 +32,8 @@ def float_round(number, ndigits=2): def server_time(message_data): return ( - datetime.fromtimestamp(message_data["server_time"], timezone.utc).isoformat() + datetime.fromtimestamp( + message_data["server_time"], timezone.utc).isoformat() + "Z" if message_data is not None and "server_time" in message_data else datetime.fromtimestamp(time.time(), timezone.utc).isoformat() + "Z" @@ -54,12 +55,15 @@ def create_nonce(length=30) -> str: return nonce # for mobile-token + + def get_user_agent(browser: str) -> str: - """try: + try: return USER_AGENTS[platform.system()][browser] except KeyError: - return USER_AGENTS["Linux"]["FIREFOX"]""" - return USER_AGENTS["Android"]["App"] + # return USER_AGENTS["Linux"]["FIREFOX"] + return USER_AGENTS["Windows"]["CHROME"] + # return USER_AGENTS["Android"]["App"] def remove_emoji(string: str) -> str: @@ -141,6 +145,7 @@ def set_default_settings(settings, defaults): '''def char_decision_as_index(char): return 0 if char == "A" else 1''' + def internet_connection_available(host="8.8.8.8", port=53, timeout=3): try: socket.setdefaulttimeout(timeout) @@ -155,7 +160,7 @@ def percentage(a, b): def create_chunks(lst, n): - return [lst[i : (i + n)] for i in range(0, len(lst), n)] # noqa: E203 + return [lst[i: (i + n)] for i in range(0, len(lst), n)] # noqa: E203 def download_file(name, fpath): diff --git a/requirements.txt b/requirements.txt index 23e6dbb7..9e67bfe2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,6 @@ flask irc pandas browser_cookie3 +selenium +selenium-wire +undetected_chromedriver diff --git a/setup.py b/setup.py index 18868643..8626e5d4 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,10 @@ def read(fname): "flask", "irc", "pandas", - "browser_cookie3" + "browser_cookie3", + "selenium", + "selenium-wire", + "undetected_chromedriver" ], long_description=read("README.md"), long_description_content_type="text/markdown", From 84b3dbe3ce1474a4130ee561ed18bd70519add90 Mon Sep 17 00:00:00 2001 From: 1v Date: Tue, 20 Dec 2022 07:20:49 +0300 Subject: [PATCH 139/140] Fix tests --- .../classes/entities/Bet.py | 25 ------------------- .../classes/entities/Strategy.py | 6 ++++- .../entities/strategies/SmartHighOdds.py | 4 +-- TwitchChannelPointsMiner/logger.py | 2 +- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/TwitchChannelPointsMiner/classes/entities/Bet.py b/TwitchChannelPointsMiner/classes/entities/Bet.py index 96072c1d..60d4a242 100644 --- a/TwitchChannelPointsMiner/classes/entities/Bet.py +++ b/TwitchChannelPointsMiner/classes/entities/Bet.py @@ -12,17 +12,6 @@ from TwitchChannelPointsMiner.utils import float_round -class Strategy(Enum): - MOST_VOTED = auto() - HIGH_ODDS = auto() - PERCENTAGE = auto() - SMART_MONEY = auto() - SMART = auto() - - def __str__(self): - return self.name - - class Condition(Enum): GT = auto() LT = auto() @@ -33,20 +22,6 @@ def __str__(self): return self.name -class OutcomeKeys(object): - # Real key on Bet dict [''] - PERCENTAGE_USERS = "percentage_users" - ODDS_PERCENTAGE = "odds_percentage" - ODDS = "odds" - TOP_POINTS = "top_points" - # Real key on Bet dict [''] - Sum() - TOTAL_USERS = "total_users" - TOTAL_POINTS = "total_points" - # This key does not exist - DECISION_USERS = "decision_users" - DECISION_POINTS = "decision_points" - - class DelayMode(Enum): FROM_START = auto() FROM_END = auto() diff --git a/TwitchChannelPointsMiner/classes/entities/Strategy.py b/TwitchChannelPointsMiner/classes/entities/Strategy.py index 76fa2228..ed917699 100644 --- a/TwitchChannelPointsMiner/classes/entities/Strategy.py +++ b/TwitchChannelPointsMiner/classes/entities/Strategy.py @@ -2,7 +2,8 @@ from importlib import import_module from random import uniform -from TwitchChannelPointsMiner.utils import char_decision_as_index +def char_decision_as_index(char): + return 0 if char == "A" else 1 class Condition(Enum): @@ -75,9 +76,12 @@ def calculate_after(self, balance: int): >= self.outcomes[index][OutcomeKeys.TOP_POINTS] ): reduce_amount = uniform(1, 5) + # check by + # grep -r --include=*.log "Bet won't be placed as the amount -[0-9] is less than the minimum required 10" . self.decision["amount"] = ( self.outcomes[index][OutcomeKeys.TOP_POINTS] - reduce_amount ) + if self.decision["amount"] < 10: self.decision["amount"] = 10 self.decision["amount"] = int(self.decision["amount"]) def calculate(self, balance: int) -> dict: diff --git a/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py index 0966fe78..88ae0b83 100644 --- a/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py +++ b/TwitchChannelPointsMiner/classes/entities/strategies/SmartHighOdds.py @@ -1,8 +1,8 @@ import logging -from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy +from TwitchChannelPointsMiner.classes.entities.Strategy import OutcomeKeys, Strategy, char_decision_as_index from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.utils import char_decision_as_index + logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/logger.py b/TwitchChannelPointsMiner/logger.py index 0faf7afb..ac76cc05 100644 --- a/TwitchChannelPointsMiner/logger.py +++ b/TwitchChannelPointsMiner/logger.py @@ -114,7 +114,7 @@ def format(self, record): and record.emoji_is_present is False ): record.msg = emoji.emojize( - f"{record.emoji} {record.msg.strip()}", language="alias" + f"{record.emoji} {record.msg.strip()}", use_aliases=True ) record.emoji_is_present = True From 800752a9a452569909fcb3fff50f0b880e30204d Mon Sep 17 00:00:00 2001 From: 1v Date: Tue, 20 Dec 2022 07:29:43 +0300 Subject: [PATCH 140/140] Remove deploy-docker.yml --- .github/workflows/deploy-docker.yml | 48 ----------------------------- 1 file changed, 48 deletions(-) delete mode 100644 .github/workflows/deploy-docker.yml diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml deleted file mode 100644 index 69f58f8f..00000000 --- a/.github/workflows/deploy-docker.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: deploy-docker - -on: - push: - branches: [master] - workflow_dispatch: - -jobs: - deploy-docker: - name: Deploy Docker Hub - runs-on: ubuntu-latest - steps: - - name: Checkout source - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2.1.0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.2.1 - - - name: Login to DockerHub - uses: docker/login-action@v2.1.0 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} - - - name: Build and push AMD64, ARM64, ARMv7 - id: docker_build - uses: docker/build-push-action@v3.2.0 - with: - context: . - push: true - tags: | - rdavidoff/twitch-channel-points-miner-v2:latest - platforms: linux/amd64,linux/arm64,linux/arm/v7 - build-args: BUILDX_QEMU_ENV=true - - # File size exceeds the maximum allowed 25000 bytes - # - name: Docker Hub Description - # uses: peter-evans/dockerhub-description@v2 - # with: - # username: ${{ secrets.DOCKER_USERNAME }} - # password: ${{ secrets.DOCKER_TOKEN }} - # repository: rdavidoff/twitch-channel-points-miner-v2 - - - name: Image digest AMD64, ARM64, ARMv7 - run: echo ${{ steps.docker_build.outputs.digest }}