diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e115913..80b236b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,13 +20,8 @@ Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. -You can enable the screenshot using: `BrowserSettings(do_screenshot=True)` - **Desktop (please complete the following information):** - OS: [e.g. Windows] - - Browser [e.g. chrome, firefox] - Python version [e.g. 3.x] **Additional context** diff --git a/README.md b/README.md index 66a0841..439a61f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# Twitch Channel Points Miner - v2 - -![Banner](./assets/banner.png) +![Twitch Channel Points Miner - v2](./assets/banner.png)

License Python3 @@ -12,7 +10,7 @@ **Credits** - Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner -- Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet +- ~~Bet system (Selenium): https://github.com/ClementRoyer/TwitchAutoCollect-AutoBet~~ > A simple script that will watch a stream for you and earn the channel points. @@ -28,9 +26,8 @@ Read more about channels point [here](https://help.twitch.tv/s/article/channel-p - Automatic download the followers list and use as input - Better 'Watch Streak' strategy in priority system [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) - Auto claim game drops from Twitch inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) Read more about game drops [here](https://help.twitch.tv/s/article/mission-based-drops) -- Place the bet / make prediction and won or lose (good luck) your channel points! **(CURRENTLY IN BETA)** - -For the bet system the script use Selenium. Could be usefull understand how to MakePrediction usign a [POST] request. I've also write a [poc](/TwitchChannelPointsMiner/classes/Twitch.py#L160) but I don't know how to calculate/create the transactionID. Any helps are welcome +- Place the bet / make prediction and won or lose (🍀) your channel points! +No browser needed. [#41](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/41) ([@lay295](https://github.com/lay295)) ### Full logs ``` @@ -47,10 +44,8 @@ For the bet system the script use Selenium. Could be usefull understand how to M %d/%m/%y %H:%M:%S - INFO - [__enable_custom_bet_value]: 🔧 Enable input of custom value for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) %d/%m/%y %H:%M:%S - INFO - [on_message]: ⏰ Place the bet after: 89.99s for: EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx-15c61914ef69, title=Please star this repo) %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +12 → Streamer(username=streamer-username, channel_id=0000000, channel_points=61377) - Reason: WATCH. -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Going to complete bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) owned by Streamer(username=streamer-username, channel_id=0000000, channel_points=61365) -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Decision: YES (PINK), Points: 156k, Users: 46 (61.33%), Odds: 1.57 (63.69%) -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Going to write: 4296 channel points on input B -%d/%m/%y %H:%M:%S - INFO - [place_bet]: 🔧 Going to place the bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) +%d/%m/%y %H:%M:%S - INFO - [make_predictions]: 🍀 Going to complete bet for EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx-15c61914ef69, title=Please star this repo) owned by Streamer(username=streamer-username, channel_id=0000000, channel_points=61377) +%d/%m/%y %H:%M:%S - INFO - [make_predictions]: 🍀 Place 5k channel points on: SI (BLUE), Points: 848k, Users: 190 (70.63%), Odds: 1.24 (80.65%) %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +6675 → Streamer(username=streamer-username, channel_id=0000000, channel_points=64206) - Reason: PREDICTION. %d/%m/%y %H:%M:%S - INFO - [on_message]: 📊 EventPrediction(event_id=xxxx-xxxx-xxxx-xxxx, title=Please star this repo) - Result: WIN, Points won: 6675 %d/%m/%y %H:%M:%S - INFO - [on_message]: 🚀 +12 → Streamer(username=streamer-username, channel_id=0000000, channel_points=64218) - Reason: WATCH. @@ -80,10 +75,8 @@ For the bet system the script use Selenium. Could be usefull understand how to M %d/%m %H:%M:%S - 🔧 Enable input of custom value for EventPrediction: Please star this repo %d/%m %H:%M:%S - ⏰ Place the bet after: 89.99s EventPrediction: Please star this repo %d/%m %H:%M:%S - 🚀 +12 → streamer-username (xxx points) - Reason: WATCH. -%d/%m %H:%M:%S - 🔧 Going to complete bet for EventPrediction: Please star this repo owned by streamer-username (xxx points) -%d/%m %H:%M:%S - 🔧 Decision: YES (PINK), Points: 156k, Users: 46 (61.33%), Odds: 1.57 (63.69%) -%d/%m %H:%M:%S - 🔧 Going to write: 4296 channel points on input B -%d/%m %H:%M:%S - 🔧 Going to place the bet for EventPrediction: Please star this repo +%d/%m %H:%M:%S - 🍀 Going to complete bet for EventPrediction: Please star this repo owned by streamer-username (xxx points) +%d/%m %H:%M:%S - 🍀 Place 5k channel points on: SI (BLUE), Points: 848k, Users: 190 (70.63%), Odds: 1.24 (80.65%) %d/%m %H:%M:%S - 🚀 +6675 → streamer-username (xxx points) - Reason: PREDICTION. %d/%m %H:%M:%S - 📊 EventPrediction: Please star this repo - Result: WIN, Points won: 6675 %d/%m %H:%M:%S - 🚀 +12 → streamer-username (xxx points) - Reason: WATCH. @@ -149,7 +142,6 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings -from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", @@ -161,11 +153,6 @@ twitch_miner = TwitchChannelPointsMiner( emoji=True, # On Windows we have a problem to print emoji. Set to false if you have a problem less=False # If you think that the logs are too much verborse set this to True ), - browser_settings=BrowserSettings( - browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser - show=False, # Show the browser during bet else headless mode - do_screenshot=False, # Do screenshot during the bet - ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction follow_raid=True, # Follow raid to obtain more points @@ -226,8 +213,6 @@ twitch_miner.mine(["streamer1", "streamer2"], followers=True) # Mixed Make sure to write the streamers array in order of priority from left to right. If you use `followers=True` Twitch return the streamers order by followed_at. So your last follow have the highest priority. -If the browser are currently betting or wait for more data It's impossible to interact with another event prediction from another streamer. - ## Settings ### LoggerSettings @@ -238,17 +223,6 @@ If the browser are currently betting or wait for more data It's impossible to in | `console_level` | level | logging.INFO | Level of logs in terminal - Use logging.DEBUG for more helpful messages. | | `file_level` | level | logging.DEBUG | Level of logs in file save - If you think the log file it's too big use logging.INFO | | `emoji` | bool | For Windows is False else True | On Windows we have a problem to print emoji. Set to false if you have a problem | -### BrowserSettings -| Key | Type | Default | Description | -|------------------- |--------- |--------- |------------------------------------------------------------------------------------------------- | -| `timeout` | float | 10 | If no element was found by Selenium raise exception after timeouts. Increase on slow connection | -| `implicitly_wait` | int | 5 | Wait x seconds after continue Selenium execution | -| `max_attempts` | int | 3 | Number of max attempt for place bet | -| `do_screenshot` | bool | False | Save screenshot before/after do some Selenium action - Help debug | -| `save_html` | bool | False | Save html content before/after do some Selenium action - Help debug | -| `show` | bool | True | Choose if you want to see or not the browser - Help debug | -| `browser` | Browser | FIREFOX | Choose your favourite browser | -| `driver_path` | str | None | Write the path of chromedriver or geckodriver | ### StreamerSettings | Key | Type | Default | Description | |-------------------- |------------- |-------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -323,30 +297,14 @@ If you already have a `twitch-cookies.pkl` and you don't want to login again ple Other users have find multiple problems on Windows my suggestion are: - Stop use Windows :stuck_out_tongue_closed_eyes: - Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` - - Download the geckodriver from here: https://github.com/mozilla/geckodriver/releases/ and extract in the same folder of this project. For other issue with geckodriver just googling: https://stackoverflow.com/questions/40208051/selenium-using-python-geckodriver-executable-needs-to-be-in-path Other usefully infos can be founded here: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner/issues/31 - -## Use Chrome instead Firefox -If you prefer Chrome instead Firefox please download the WebDriver matching with your Chrome version and OS from this link: https://chromedriver.chromium.org/downloads. -Extract the archivie, copy the chromedriver file in this project folder. -Edit your run.py file and the browser_settings should something like this: -```python -browser_settings=BrowserSettings( - browser=Browser.CHROME, - driver_path="/path/of/your/chromedriver" # If no path was provided the script will try to search automatically -), -``` - ## Issue / Debug When you open a new issue please use the correct template. Please provide at least the following information/files: -- Browser (if you have the prediction feature enabled) - Operation System - Python Version - logs/ `LoggerSettings(file_level=logging.DEBUG)` -- htmls/ `BrowserSettings(save_html=True)` -- screenshots/ `BrowserSettings(do_screenshot=True)` Make sure also to have the latest commit. diff --git a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py index d7b16e1..941b637 100644 --- a/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py +++ b/TwitchChannelPointsMiner/TwitchChannelPointsMiner.py @@ -19,10 +19,6 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import ( from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Twitch import Twitch -from TwitchChannelPointsMiner.classes.TwitchBrowser import ( - BrowserSettings, - TwitchBrowser, -) from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers from TwitchChannelPointsMiner.utils import ( @@ -32,11 +28,6 @@ from TwitchChannelPointsMiner.utils import ( set_default_settings, ) -# Suppress warning for urllib3.connectionpool (selenium close connection) -# Suppress also the selenium logger please -logging.getLogger("urllib3").setLevel(logging.ERROR) -logging.getLogger("selenium").setLevel(logging.ERROR) - logger = logging.getLogger(__name__) @@ -45,10 +36,8 @@ class TwitchChannelPointsMiner: self, username: str, claim_drops_startup: bool = False, - # Settings for logging and selenium as you can see. # This settings will be global shared trought Settings class logger_settings: LoggerSettings = LoggerSettings(), - browser_settings: BrowserSettings = BrowserSettings(), # Default values for all streamers streamer_settings: StreamerSettings = StreamerSettings(), ): @@ -56,17 +45,15 @@ class TwitchChannelPointsMiner: # Set as globally config Settings.logger = logger_settings - Settings.browser = browser_settings # Init as default all the missing values streamer_settings.default() streamer_settings.bet.default() Settings.streamer_settings = streamer_settings - user_agent = get_user_agent(browser_settings.browser) + user_agent = get_user_agent("FIREFOX") self.twitch = Twitch(self.username, user_agent) - self.twitch_browser = None self.claim_drops_startup = claim_drops_startup self.streamers = [] self.events_predictions = {} @@ -175,14 +162,6 @@ class TwitchChannelPointsMiner: make_predictions = at_least_one_value_in_settings_is( self.streamers, "make_predictions", True ) - # We need a browser to make predictions / bet - if make_predictions is True: - self.twitch_browser = TwitchBrowser( - self.twitch.twitch_login.get_auth_token(), - self.session_id, - settings=Settings.browser, - ) - self.twitch_browser.init() self.minute_watcher_thread = threading.Thread( target=self.twitch.send_minute_watched_events, @@ -197,7 +176,6 @@ class TwitchChannelPointsMiner: self.ws_pool = WebSocketsPool( twitch=self.twitch, - browser=self.twitch_browser, streamers=self.streamers, events_predictions=self.events_predictions, ) @@ -262,16 +240,13 @@ class TwitchChannelPointsMiner: def end(self, signum, frame): logger.info("CTRL+C Detected! Please wait just a moments!") - if self.twitch_browser is not None: - self.twitch_browser.browser.quit() - self.running = self.twitch.running = False self.ws_pool.end() self.minute_watcher_thread.join() self.__print_report() - time.sleep(3.5) # Do sleep for ending browser and threads + time.sleep(3.5) # Do sleep for ending threads ... sys.exit(0) diff --git a/TwitchChannelPointsMiner/__init__.py b/TwitchChannelPointsMiner/__init__.py index a37153e..4c150ca 100644 --- a/TwitchChannelPointsMiner/__init__.py +++ b/TwitchChannelPointsMiner/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "2.5.0" +__version__ = "2.7.1" from .TwitchChannelPointsMiner import TwitchChannelPointsMiner __all__ = [ diff --git a/TwitchChannelPointsMiner/classes/Twitch.py b/TwitchChannelPointsMiner/classes/Twitch.py index c8efcb4..b9595a0 100644 --- a/TwitchChannelPointsMiner/classes/Twitch.py +++ b/TwitchChannelPointsMiner/classes/Twitch.py @@ -11,6 +11,7 @@ import random import re import time from pathlib import Path +from secrets import token_hex import requests @@ -21,7 +22,8 @@ from TwitchChannelPointsMiner.classes.Exceptions import ( ) from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin -from TwitchChannelPointsMiner.constants.twitch import API, CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.constants import API, CLIENT_ID, GQLOperations +from TwitchChannelPointsMiner.utils import _millify logger = logging.getLogger(__name__) @@ -198,13 +200,24 @@ class Twitch(object): def make_predictions(self, event): decision = event.bet.calculate(event.streamer.channel_points) + selector_index = 0 if decision["choice"] == "A" else 1 + + logger.info( + f"Going to complete bet for {event} owned by {event.streamer}", + extra={"emoji": ":four_leaf_clover:"}, + ) + logger.info( + f"Place {_millify(decision['amount'])} channel points on: {event.bet.get_outcome(selector_index)}", + extra={"emoji": ":four_leaf_clover:"}, + ) + json_data = copy.deepcopy(GQLOperations.MakePrediction) json_data["variables"] = { "input": { "eventID": event.event_id, "outcomeID": decision["id"], "points": decision["amount"], - "transactionID": "412118d3********79ac856", # How we can calculate this? + "transactionID": token_hex(16), } } return self.post_gql_request(json_data) diff --git a/TwitchChannelPointsMiner/classes/TwitchBrowser.py b/TwitchChannelPointsMiner/classes/TwitchBrowser.py deleted file mode 100644 index 2e12a31..0000000 --- a/TwitchChannelPointsMiner/classes/TwitchBrowser.py +++ /dev/null @@ -1,438 +0,0 @@ -import logging -import os -import platform -import random -import time -from enum import Enum, auto -from pathlib import Path - -from selenium import webdriver -from selenium.common.exceptions import JavascriptException -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions -from selenium.webdriver.support.ui import WebDriverWait - -from TwitchChannelPointsMiner.classes.entities.EventPrediction import EventPrediction -from TwitchChannelPointsMiner.constants.browser import Javascript, Selectors -from TwitchChannelPointsMiner.constants.twitch import URL -from TwitchChannelPointsMiner.utils import ( - _millify, - bet_condition, - char_decision_as_index, - get_user_agent, -) - -logger = logging.getLogger(__name__) - - -class Browser(Enum): - CHROME = auto() - FIREFOX = auto() - - def __str__(self): - return self.name - - -class BrowserSettings: - def __init__( - self, - timeout: float = 10.0, - implicitly_wait: int = 5, - max_attempts: int = 3, - do_screenshot: bool = False, # Options for debug - save_html: bool = False, # Options for debug - show: bool = True, - browser: Browser = Browser.FIREFOX, - driver_path: str = None, - ): - self.timeout = timeout - self.implicitly_wait = implicitly_wait - self.max_attempts = max_attempts - self.do_screenshot = do_screenshot - self.save_html = save_html - self.show = show - self.browser = browser - self.driver_path = ( - driver_path - if driver_path is not None - else os.path.join( - Path().absolute(), - ( - ("chromedriver" if browser == Browser.CHROME else "geckodriver") - + (".exe" if platform.system() == "Windows" else "") - ), - ) - ) - - -class TwitchBrowser(object): - def __init__( - self, - auth_token: str, - session_id: str, - settings: BrowserSettings, - ): - self.auth_token = auth_token - self.session_id = session_id - self.settings = settings - - self.currently_is_betting = False - self.browser = None - - def init(self): - if self.settings.browser == Browser.FIREFOX: - self.__init_firefox() - elif self.settings.browser == Browser.CHROME: - self.__init_chrome() - - if self.browser is not None: - self.browser.set_window_size(450, 800) - self.browser.implicitly_wait(self.settings.implicitly_wait) - - self.__init_twitch() - - def __init_twitch(self): - logger.debug( - "Init Twitch page - Cookies - LocalStorage items", - extra={"emoji": ":wrench:"}, - ) - cookie = { - "domain": ".twitch.tv", - "hostOnly": False, - "httpOnly": False, - "name": "auth-token", - "path": "/", - "SameSite": "no_restriction", - "secure": True, - "session": False, - "storeId": "0", - "id": 1, - "value": self.auth_token, - } - self.browser.get(URL) - self.browser.add_cookie(cookie) - time.sleep(random.uniform(2.5, 3.5)) - - self.__click_when_exist( - Selectors.cookiePolicy, By.CSS_SELECTOR, suppress_error=True - ) - time.sleep(random.uniform(0.5, 1.5)) - - # Edit value in localStorage for dark theme, point consent etc. - self.__execute_script(Javascript.localStorage) - time.sleep(random.uniform(0.5, 1.5)) - self.__blank() - - def __blank(self): - self.browser.get("about:blank") - - def __execute_script(self, javascript_code, suppress_error=False): - try: - self.browser.execute_script(javascript_code) - return True - except JavascriptException: - if suppress_error is False: - logger.warning(f"Failed to execute: {javascript_code}") - return False - - # Private method __ - We can instantiate webdriver only with init_browser - def __init_chrome(self): - logger.debug("Init Chrome browser", extra={"emoji": ":wrench:"}) - options = webdriver.ChromeOptions() - if not self.settings.show: - options.add_argument("headless") - - options.add_argument("mute-audio") - options.add_argument("disable-dev-shm-usage") - options.add_argument("disable-accelerated-2d-canvas") - options.add_argument("no-first-run") - options.add_argument("no-zygote") - options.add_argument("disable-gpu") - options.add_argument("no-sandbox") - options.add_argument("disable-setuid-sandbox") - options.add_argument("disable-infobars") - options.add_argument(f"user-agent={get_user_agent(self.settings.browser)}") - - options.add_experimental_option( - "prefs", {"profile.managed_default_content_settings.images": 2} - ) - options.add_experimental_option("useAutomationExtension", False) - options.add_experimental_option( - "excludeSwitches", ["enable-automation", "enable-logging"] - ) - - if os.path.isfile(self.settings.driver_path) is True: - self.browser = webdriver.Chrome(self.settings.driver_path, options=options) - else: - logger.warning( - f"The path {self.settings.driver_path} is not valid. Using default path...", - extra={"emoji": ":wrench:"}, - ) - self.browser = webdriver.Chrome(options=options) - - # Private method __ - We can instantiate webdriver only with init_browser - def __init_firefox(self): - logger.debug("Init Firefox browser", extra={"emoji": ":wrench:"}) - options = webdriver.FirefoxOptions() - if not self.settings.show: - options.headless = True - - fp = webdriver.FirefoxProfile() - fp.set_preference("permissions.default.image", 2) - fp.set_preference("permissions.default.stylesheet", 2) - fp.set_preference("dom.ipc.plugins.enabled.libflashplayer.so", "false") - fp.set_preference("media.volume_scale", "0.0") - fp.set_preference("browser.startup.homepage", "about:blank") - fp.set_preference("startup.homepage_welcome_url", "about:blank") - fp.set_preference("startup.homepage_welcome_url.additional", "about:blank") - fp.set_preference( - "general.useragent.override", - get_user_agent(self.settings.browser), - ) - - if os.path.isfile(self.settings.driver_path) is True: - self.browser = webdriver.Firefox( - executable_path=self.settings.driver_path, - options=options, - firefox_profile=fp, - ) - else: - logger.warning( - f"The path {self.settings.driver_path} is not valid. Using default path...", - extra={"emoji": ":wrench:"}, - ) - self.browser = webdriver.Firefox(options=options, firefox_profile=fp) - - def __debug(self, event, method): - if self.settings.do_screenshot: - self.screenshot(f"{event.event_id}___{method}") - if self.settings.save_html: - self.save_html(f"{event.event_id}___{method}") - - def save_html(self, fname): - htmls_path = os.path.join(Path().absolute(), "htmls") - Path(htmls_path).mkdir(parents=True, exist_ok=True) - Path(os.path.join(htmls_path, self.session_id)).mkdir( - parents=True, exist_ok=True - ) - - fname = f"{fname}.html" if fname.endswith(".html") is False else fname - fname = fname.replace(".html", f".{time.time()}.html") - fname = os.path.join(htmls_path, self.session_id, fname) - - # Little delay ... - time.sleep(0.2) - with open(fname, "w", encoding="utf-8") as writer: - writer.write(self.browser.page_source) - - def screenshot(self, fname): - screenshots_path = os.path.join(Path().absolute(), "screenshots") - Path(screenshots_path).mkdir(parents=True, exist_ok=True) - Path(os.path.join(screenshots_path, self.session_id)).mkdir( - parents=True, exist_ok=True - ) - - fname = f"{fname}.png" if fname.endswith(".png") is False else fname - fname = fname.replace(".png", f".{time.time()}.png") - fname = os.path.join(screenshots_path, self.session_id, fname) - # Little pause prevent effect/css animations in browser delayed - time.sleep(0.1) - self.browser.save_screenshot(fname) - - def __click_when_exist( - self, selector, by: By = By.CSS_SELECTOR, suppress_error=False, timeout=None - ) -> bool: - timeout = self.settings.timeout if timeout is None else timeout - try: - element = WebDriverWait(self.browser, timeout).until( - expected_conditions.element_to_be_clickable((by, selector)) - ) - ActionChains(self.browser).move_to_element(element).click().perform() - return True - except Exception: - if suppress_error is False: - logger.error(f"Exception raised with: {selector}", exc_info=True) - return False - - def __send_text( - self, selector, text, by: By = By.CSS_SELECTOR, suppress_error=False - ) -> bool: - try: - element = WebDriverWait(self.browser, self.settings.timeout).until( - expected_conditions.element_to_be_clickable((by, selector)) - ) - ActionChains(self.browser).move_to_element(element).click().send_keys( - text - ).perform() - return True - except Exception: - if suppress_error is False: - logger.error(f"Exception raised with: {selector}", exc_info=True) - return False - - def start_bet(self, event: EventPrediction): - if bet_condition(self, event, logger) is True: - for attempt in range(0, self.settings.max_attempts): - logger.info( - f"Starting betting for {event} owned by {event.streamer}", - extra={"emoji": ":wrench:"}, - ) - self.browser.get(event.streamer.chat_url) - time.sleep(random.uniform(1, 3)) - self.__click_when_exist( - Selectors.cookiePolicy, - By.CSS_SELECTOR, - suppress_error=True, - timeout=1.5, - ) - - # Hide the chat ... Don't ask me why - self.__execute_script(Javascript.clearStyleChat, suppress_error=True) - - if self.__bet_chains_methods(event) is True: - return self.currently_is_betting - logger.error( - f"Attempt {attempt+1} failed!", extra={"emoji": ":wrench:"} - ) - self.__blank() # If we fail return to blank page - return False - - def __bet_chains_methods(self, event) -> bool: - if self.__open_coins_menu(event) is True: - if self.__click_on_bet(event) is True: - if self.__enable_custom_bet_value(event) is True: - return True - return False - - def place_bet(self, event: EventPrediction): - logger.info( - f"Place bet for {event} owned by {event.streamer}", - extra={"emoji": ":wrench:"}, - ) - if event.status == "ACTIVE": - decision = event.bet.calculate(event.streamer.channel_points) - if event.bet.skip() is True: - logger.info(f"Skip betting for the event {event}") - logger.info(f"Skip settings {event.bet.settings.filter_condition}") - else: - self.currently_is_betting = self.start_bet(event) - if event.box_fillable and self.currently_is_betting: - if decision["choice"] is not None: - selector_index = char_decision_as_index(decision["choice"]) + 1 - logger.info( - f"Decision: {event.bet.get_outcome(selector_index - 1)}", - extra={"emoji": ":wrench:"}, - ) - - try: - logger.info( - f"Going to write: {_millify(decision['amount'])} channel points on input {decision['choice']}", - extra={"emoji": ":wrench:"}, - ) - if ( - self.__send_text_on_bet( - event, selector_index, decision["amount"] - ) - is True - ): - logger.info( - f"Going to place the bet for {event}", - extra={"emoji": ":wrench:"}, - ) - if self.__click_on_vote(event, selector_index) is True: - event.bet_placed = True - time.sleep(random.uniform(3, 6)) - except Exception: - logger.error("Exception raised", exc_info=True) - else: - logger.info( - f"Sorry, unable to complete the bet. Event box fillable: {event.box_fillable}, the browser is betting: {self.currently_is_betting}" - ) - else: - logger.info( - f"Oh no! The event is not active anymore! Current status: {event.status}", - extra={"emoji": ":disappointed_relieved:"}, - ) - - self.browser.get("about:blank") - self.currently_is_betting = False - - def __open_coins_menu(self, event: EventPrediction) -> bool: - logger.info(f"Opening coins menu for {event}", extra={"emoji": ":wrench:"}) - status = self.__click_when_exist(Selectors.coinsMenuXP, By.XPATH) - if status is False: - status = self.__execute_script(Javascript.coinsMenu) - - if status is True: - time.sleep(random.uniform(0.01, 0.1)) - self.__debug(event, "open_coins_menu") - return True - return False - - def __click_on_bet(self, event, maximize_div=True) -> bool: - logger.info(f"Clicking on the bet for {event}", extra={"emoji": ":wrench:"}) - if self.__click_when_exist(Selectors.betTitle, By.CSS_SELECTOR) is True: - time.sleep(random.uniform(0.01, 0.1)) - if maximize_div is True: - # Edit the css for make the window full-screen in browser. Another useless change - self.__execute_script(Javascript.maximizeBetWindow, suppress_error=True) - self.__debug(event, "click_on_bet") - return True - return False - - def __enable_custom_bet_value(self, event, scroll_down=True) -> bool: - logger.info( - f"Enable input of custom value for {event}", - extra={"emoji": ":wrench:"}, - ) - - if scroll_down is True: - time.sleep(random.uniform(0.01, 0.1)) - if self.__execute_script(Javascript.scrollDownBetWindow) is False: - logger.error("Unable to scroll down in the bet window!") - - status = self.__click_when_exist(Selectors.betCustomVote, By.CSS_SELECTOR) - if status is False: - status = self.__execute_script(Javascript.betCustomVote) - - if status is True: - time.sleep(random.uniform(0.01, 0.1)) - self.__debug(event, "enable_custom_bet_value") - event.box_fillable = True - self.currently_is_betting = True - return True - else: - logger.info( - "Something went wrong unable to continue with betting - Fillable box not available!" - ) - return False - - def __send_text_on_bet(self, event, selector_index, text) -> bool: - self.__debug(event, "before__send_text") - status = self.__send_text( - f"{Selectors.betVoteInputXP}[{selector_index}]", text, By.XPATH - ) - if status is False: - status = self.__execute_script( - Javascript.betVoteInput.format(int(selector_index) - 1, int(text)) - ) - - if status is True: - self.__debug(event, "send_text") - return True - return False - - def __click_on_vote(self, event, selector_index) -> bool: - status = self.__click_when_exist( - f"{Selectors.betVoteButtonXP}[{selector_index}]", By.XPATH - ) - if status is False: - status = self.__execute_script( - Javascript.betVoteButton.format(int(selector_index) - 1) - ) - - if status is True: - self.__debug(event, "click_on_vote") - return True - return False diff --git a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py index 9c81973..aaf595c 100644 --- a/TwitchChannelPointsMiner/classes/TwitchWebSocket.py +++ b/TwitchChannelPointsMiner/classes/TwitchWebSocket.py @@ -26,7 +26,6 @@ class TwitchWebSocket(WebSocketApp): self.pending_topics = [] self.twitch = parent_pool.twitch - self.browser = parent_pool.browser self.streamers = parent_pool.streamers self.events_predictions = parent_pool.events_predictions diff --git a/TwitchChannelPointsMiner/classes/WebSocketsPool.py b/TwitchChannelPointsMiner/classes/WebSocketsPool.py index c184516..881e2d4 100644 --- a/TwitchChannelPointsMiner/classes/WebSocketsPool.py +++ b/TwitchChannelPointsMiner/classes/WebSocketsPool.py @@ -11,17 +11,16 @@ from TwitchChannelPointsMiner.classes.entities.Message import Message from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket -from TwitchChannelPointsMiner.constants.twitch import WEBSOCKET -from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_streamer_index +from TwitchChannelPointsMiner.constants import WEBSOCKET +from TwitchChannelPointsMiner.utils import _millify, get_streamer_index logger = logging.getLogger(__name__) -class WebSocketsPool(object): - def __init__(self, twitch, browser, streamers, events_predictions): - self.ws: list = [] +class WebSocketsPool: + def __init__(self, twitch, streamers, events_predictions): + self.ws = [] self.twitch = twitch - self.browser = browser self.streamers = streamers self.events_predictions = events_predictions @@ -214,29 +213,29 @@ class WebSocketsPool(object): if ( ws.streamers[streamer_index].is_online and event.closing_bet_after(current_tmsp) > 0 - and bet_condition( - ws.browser, - event, - logger, - ) - is True ): - # place_bet_thread = threading.Timer(event.closing_bet_after(current_tmsp), ws.twitch.make_predictions, (ws.events_predictions[event_id],)) - ws.events_predictions[event_id] = event - start_after = event.closing_bet_after(current_tmsp) + if event.streamer.viewer_is_mod is True: + logger.info( + f"Sorry, you are moderator of {event.streamer}, so you can't bet!" + ) + else: + ws.events_predictions[event_id] = event + start_after = event.closing_bet_after( + current_tmsp + ) - place_bet_thread = threading.Timer( - start_after, - ws.browser.place_bet, - (ws.events_predictions[event_id],), - ) - place_bet_thread.daemon = True - place_bet_thread.start() + place_bet_thread = threading.Timer( + start_after, + ws.twitch.make_predictions, + (ws.events_predictions[event_id],), + ) + place_bet_thread.daemon = True + place_bet_thread.start() - logger.info( - f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", - extra={"emoji": ":alarm_clock:"}, - ) + logger.info( + f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", + extra={"emoji": ":alarm_clock:"}, + ) elif ( message.type == "event-updated" diff --git a/TwitchChannelPointsMiner/classes/entities/Stream.py b/TwitchChannelPointsMiner/classes/entities/Stream.py index ad68d4d..affce68 100644 --- a/TwitchChannelPointsMiner/classes/entities/Stream.py +++ b/TwitchChannelPointsMiner/classes/entities/Stream.py @@ -4,7 +4,7 @@ import time from base64 import b64encode from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.constants.twitch import DROP_ID +from TwitchChannelPointsMiner.constants import DROP_ID logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/classes/entities/Streamer.py b/TwitchChannelPointsMiner/classes/entities/Streamer.py index a97b774..3eee2b6 100644 --- a/TwitchChannelPointsMiner/classes/entities/Streamer.py +++ b/TwitchChannelPointsMiner/classes/entities/Streamer.py @@ -4,7 +4,7 @@ import time from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings from TwitchChannelPointsMiner.classes.entities.Stream import Stream from TwitchChannelPointsMiner.classes.Settings import Settings -from TwitchChannelPointsMiner.constants.twitch import URL +from TwitchChannelPointsMiner.constants import URL from TwitchChannelPointsMiner.utils import _millify logger = logging.getLogger(__name__) diff --git a/TwitchChannelPointsMiner/constants/twitch.py b/TwitchChannelPointsMiner/constants.py similarity index 84% rename from TwitchChannelPointsMiner/constants/twitch.py rename to TwitchChannelPointsMiner/constants.py index 9e26837..4969f08 100644 --- a/TwitchChannelPointsMiner/constants/twitch.py +++ b/TwitchChannelPointsMiner/constants.py @@ -5,6 +5,17 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" +USER_AGENTS = { + "Windows": { + "CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", + "FIREFOX": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + }, + "Linux": { + "CHROME": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", + "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", + }, +} + class GQLOperations: url = "https://gql.twitch.tv/gql" diff --git a/TwitchChannelPointsMiner/constants/__init__.py b/TwitchChannelPointsMiner/constants/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/TwitchChannelPointsMiner/constants/browser.py b/TwitchChannelPointsMiner/constants/browser.py deleted file mode 100644 index 9565af5..0000000 --- a/TwitchChannelPointsMiner/constants/browser.py +++ /dev/null @@ -1,65 +0,0 @@ -USER_AGENTS = { - "Windows": { - "CHROME": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", - "FIREFOX": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", - }, - "Linux": { - "CHROME": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36", - "FIREFOX": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0", - }, -} - - -class Selectors: - # XPath Selector and Javascript helpers - cookiePolicy = 'button[data-a-target="consent-banner-accept"]' - coinsMenuXP = '//div[@data-test-selector="community-points-summary"]//button' - betTitle = '[data-test-selector="predictions-list-item__title"]' - betCustomVote = "button[data-test-selector='prediction-checkout-active-footer__input-type-toggle']" - betMainDivXP = "//div[@id='channel-points-reward-center-body']//div[contains(@class,'custom-prediction-button')]" - betVoteInputXP = f"({betMainDivXP}//input)" - betVoteButtonXP = f"({betMainDivXP}//button)" - - -class Javascript: - # Helpers for selenium. I think is very useless - coinsMenu = 'document.querySelector("[data-test-selector=\'community-points-summary\']").getElementsByTagName("button")[0].click();' - betVoteInput = 'document.getElementById("channel-points-reward-center-body").getElementsByTagName("input")[{}].value = {};' - betVoteButton = 'document.getElementById("channel-points-reward-center-body").getElementsByTagName("button")[{}].click();' - betCustomVote = f'document.querySelector("{Selectors.betCustomVote}").click();' - - # Some Javascript code that should help the script - localStorage = """ - window.localStorage.setItem("volume", 0); - window.localStorage.setItem("channelPointsOnboardingDismissed", true); - window.localStorage.setItem("twilight.theme", 1); - window.localStorage.setItem("mature", true); - window.localStorage.setItem("rebrand-notice-dismissed", true); - window.localStorage.setItem("emoteAnimationsEnabled", false); - window.localStorage.setItem("chatPauseSetting", "ALTKEY"); - """ - clearStyleChat = """ - var item = document.querySelector('[data-test-selector="chat-scrollable-area__message-container"]'); - if (item) { - var parent = item.closest("div.simplebar-scroll-content"); - if(parent) parent.hidden = true; - } - var header = document.querySelector('[data-test-selector="channel-leaderboard-container"]'); - if(header) header.hidden = true; - """ - maximizeBetWindow = """ - var absolute = document.querySelector('[aria-describedby="channel-points-reward-center-body"]').closest("div.tw-absolute") - if(absolute) absolute.classList.remove("tw-absolute") - - document.getElementsByClassName("reward-center__content")[0].style.width = "44rem"; - document.getElementsByClassName("reward-center__content")[0].style.height = "55rem"; - - document.querySelector('[aria-describedby="channel-points-reward-center-body"]').style["max-height"] = "55rem"; - - document.getElementsByClassName("reward-center-body")[0].style["max-width"] = "44rem"; - // document.getElementsByClassName("reward-center-body")[0].style["min-height"] = "55rem"; - """ - scrollDownBetWindow = """ - var scrollable = document.getElementById("channel-points-reward-center-body").closest("div.simplebar-scroll-content"); - scrollable.scrollTop = scrollable.scrollHeight; - """ diff --git a/TwitchChannelPointsMiner/utils.py b/TwitchChannelPointsMiner/utils.py index b7d9e63..26d2483 100644 --- a/TwitchChannelPointsMiner/utils.py +++ b/TwitchChannelPointsMiner/utils.py @@ -7,7 +7,7 @@ from random import randrange from millify import millify -from TwitchChannelPointsMiner.constants.browser import USER_AGENTS +from TwitchChannelPointsMiner.constants import USER_AGENTS def _millify(input, precision=2): @@ -51,24 +51,7 @@ def create_nonce(length=30) -> str: return nonce -def bet_condition(twitch_browser, event, logger) -> bool: - if twitch_browser.currently_is_betting is True: - logger.info( - f"Sorry, unable to start {event}, the browser is currently betting on another event!" - ) - return False - elif twitch_browser.browser.current_url != "about:blank": - logger.info( - "Sorry, but the browser is not currently on 'about:blank' screen. Unable to start bet!" - ) - return False - elif event.streamer.viewer_is_mod is True: - logger.info(f"Sorry, you are moderator of {event.streamer}, so you can't bet!") - return False - return True - - -def get_user_agent(browser) -> str: +def get_user_agent(browser: str) -> str: try: return USER_AGENTS[platform.system()][browser] except KeyError: diff --git a/example.py b/example.py index c520299..be6dc22 100644 --- a/example.py +++ b/example.py @@ -5,7 +5,6 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner from TwitchChannelPointsMiner.logger import LoggerSettings from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings -from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings twitch_miner = TwitchChannelPointsMiner( username="your-twitch-username", @@ -17,11 +16,6 @@ twitch_miner = TwitchChannelPointsMiner( emoji=True, # On Windows we have a problem to print emoji. Set to false if you have a problem less=False # If you think that the logs are too much verborse set this to True ), - browser_settings=BrowserSettings( - browser=Browser.FIREFOX, # Choose if you want to use Chrome or Firefox as browser - show=False, # Show the browser during bet else headless mode - do_screenshot=False, # Do screenshot during the bet - ), streamer_settings=StreamerSettings( make_predictions=True, # If you want to Bet / Make prediction follow_raid=True, # Follow raid to obtain more points diff --git a/requirements.txt b/requirements.txt index c9b056d..46f7d58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ requests websocket-client browser_cookie3 pillow -selenium python-dateutil emoji millify