Merge pull request #50 from Tkd-Alex/off-selenium

Place the bet without Selenium
This commit is contained in:
Alessandro Maggio
2021-02-07 15:29:25 +01:00
committed by GitHub
16 changed files with 66 additions and 643 deletions

View File

@@ -20,13 +20,8 @@ Steps to reproduce the behavior:
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. 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):** **Desktop (please complete the following information):**
- OS: [e.g. Windows] - OS: [e.g. Windows]
- Browser [e.g. chrome, firefox]
- Python version [e.g. 3.x] - Python version [e.g. 3.x]
**Additional context** **Additional context**

View File

@@ -1,6 +1,4 @@
# Twitch Channel Points Miner - v2 ![Twitch Channel Points Miner - v2](./assets/banner.png)
![Banner](./assets/banner.png)
<p align="center"> <p align="center">
<a href="https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/LICENSE"><img alt="License" src="https://img.shields.io/github/license/Tkd-Alex/Twitch-Channel-Points-Miner-v2"></a> <a href="https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/blob/master/LICENSE"><img alt="License" src="https://img.shields.io/github/license/Tkd-Alex/Twitch-Channel-Points-Miner-v2"></a>
<a href="https://www.python.org/download/releases/3.0/"><img alt="Python3" src="https://img.shields.io/badge/built%20with-Python3-red.svg?style=flat"></a> <a href="https://www.python.org/download/releases/3.0/"><img alt="Python3" src="https://img.shields.io/badge/built%20with-Python3-red.svg?style=flat"></a>
@@ -12,7 +10,7 @@
**Credits** **Credits**
- Main idea: https://github.com/gottagofaster236/Twitch-Channel-Points-Miner - 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. > 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 - 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) - 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) - 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)** - 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))
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
### Full logs ### 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 - [__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]: ⏰ 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 - [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 - [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 - [place_bet]: 🔧 Decision: YES (PINK), Points: 156k, Users: 46 (61.33%), Odds: 1.57 (63.69%) %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 - [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 - [on_message]: 🚀 +6675 → Streamer(username=streamer-username, channel_id=0000000, channel_points=64206) - Reason: PREDICTION. %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]: 📊 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. %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 - 🔧 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 - ⏰ 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 - 🚀 +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 - 🍀 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 - 🍀 Place 5k channel points on: SI (BLUE), Points: 848k, Users: 190 (70.63%), Odds: 1.24 (80.65%)
%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 - 🚀 +6675 → streamer-username (xxx points) - Reason: PREDICTION. %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 - 📊 EventPrediction: Please star this repo - Result: WIN, Points won: 6675
%d/%m %H:%M:%S - 🚀 +12 → streamer-username (xxx points) - Reason: WATCH. %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.logger import LoggerSettings
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition
from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings
from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings
twitch_miner = TwitchChannelPointsMiner( twitch_miner = TwitchChannelPointsMiner(
username="your-twitch-username", 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 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 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( streamer_settings=StreamerSettings(
make_predictions=True, # If you want to Bet / Make prediction make_predictions=True, # If you want to Bet / Make prediction
follow_raid=True, # Follow raid to obtain more points 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. 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 ## Settings
### LoggerSettings ### 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. | | `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 | | `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 | | `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 ### StreamerSettings
| Key | Type | Default | Description | | 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: Other users have find multiple problems on Windows my suggestion are:
- Stop use Windows :stuck_out_tongue_closed_eyes: - Stop use Windows :stuck_out_tongue_closed_eyes:
- Suppress the emoji in logs with `logger_settings=LoggerSettings(emoji=False)` - 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 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 ## Issue / Debug
When you open a new issue please use the correct template. When you open a new issue please use the correct template.
Please provide at least the following information/files: Please provide at least the following information/files:
- Browser (if you have the prediction feature enabled)
- Operation System - Operation System
- Python Version - Python Version
- logs/ `LoggerSettings(file_level=logging.DEBUG)` - logs/ `LoggerSettings(file_level=logging.DEBUG)`
- htmls/ `BrowserSettings(save_html=True)`
- screenshots/ `BrowserSettings(do_screenshot=True)`
Make sure also to have the latest commit. Make sure also to have the latest commit.

View File

@@ -19,10 +19,6 @@ from TwitchChannelPointsMiner.classes.entities.Streamer import (
from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException from TwitchChannelPointsMiner.classes.Exceptions import StreamerDoesNotExistException
from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Settings import Settings
from TwitchChannelPointsMiner.classes.Twitch import Twitch from TwitchChannelPointsMiner.classes.Twitch import Twitch
from TwitchChannelPointsMiner.classes.TwitchBrowser import (
BrowserSettings,
TwitchBrowser,
)
from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool from TwitchChannelPointsMiner.classes.WebSocketsPool import WebSocketsPool
from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers from TwitchChannelPointsMiner.logger import LoggerSettings, configure_loggers
from TwitchChannelPointsMiner.utils import ( from TwitchChannelPointsMiner.utils import (
@@ -32,11 +28,6 @@ from TwitchChannelPointsMiner.utils import (
set_default_settings, 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__) logger = logging.getLogger(__name__)
@@ -45,10 +36,8 @@ class TwitchChannelPointsMiner:
self, self,
username: str, username: str,
claim_drops_startup: bool = False, claim_drops_startup: bool = False,
# Settings for logging and selenium as you can see.
# This settings will be global shared trought Settings class # This settings will be global shared trought Settings class
logger_settings: LoggerSettings = LoggerSettings(), logger_settings: LoggerSettings = LoggerSettings(),
browser_settings: BrowserSettings = BrowserSettings(),
# Default values for all streamers # Default values for all streamers
streamer_settings: StreamerSettings = StreamerSettings(), streamer_settings: StreamerSettings = StreamerSettings(),
): ):
@@ -56,17 +45,15 @@ class TwitchChannelPointsMiner:
# Set as globally config # Set as globally config
Settings.logger = logger_settings Settings.logger = logger_settings
Settings.browser = browser_settings
# Init as default all the missing values # Init as default all the missing values
streamer_settings.default() streamer_settings.default()
streamer_settings.bet.default() streamer_settings.bet.default()
Settings.streamer_settings = streamer_settings 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 = Twitch(self.username, user_agent)
self.twitch_browser = None
self.claim_drops_startup = claim_drops_startup self.claim_drops_startup = claim_drops_startup
self.streamers = [] self.streamers = []
self.events_predictions = {} self.events_predictions = {}
@@ -175,14 +162,6 @@ class TwitchChannelPointsMiner:
make_predictions = at_least_one_value_in_settings_is( make_predictions = at_least_one_value_in_settings_is(
self.streamers, "make_predictions", True 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( self.minute_watcher_thread = threading.Thread(
target=self.twitch.send_minute_watched_events, target=self.twitch.send_minute_watched_events,
@@ -197,7 +176,6 @@ class TwitchChannelPointsMiner:
self.ws_pool = WebSocketsPool( self.ws_pool = WebSocketsPool(
twitch=self.twitch, twitch=self.twitch,
browser=self.twitch_browser,
streamers=self.streamers, streamers=self.streamers,
events_predictions=self.events_predictions, events_predictions=self.events_predictions,
) )
@@ -262,16 +240,13 @@ class TwitchChannelPointsMiner:
def end(self, signum, frame): def end(self, signum, frame):
logger.info("CTRL+C Detected! Please wait just a moments!") 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.running = self.twitch.running = False
self.ws_pool.end() self.ws_pool.end()
self.minute_watcher_thread.join() self.minute_watcher_thread.join()
self.__print_report() 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) sys.exit(0)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = "2.5.0" __version__ = "2.7.1"
from .TwitchChannelPointsMiner import TwitchChannelPointsMiner from .TwitchChannelPointsMiner import TwitchChannelPointsMiner
__all__ = [ __all__ = [

View File

@@ -11,6 +11,7 @@ import random
import re import re
import time import time
from pathlib import Path from pathlib import Path
from secrets import token_hex
import requests import requests
@@ -21,7 +22,8 @@ from TwitchChannelPointsMiner.classes.Exceptions import (
) )
from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Settings import Settings
from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin 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__) logger = logging.getLogger(__name__)
@@ -198,13 +200,24 @@ class Twitch(object):
def make_predictions(self, event): def make_predictions(self, event):
decision = event.bet.calculate(event.streamer.channel_points) 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 = copy.deepcopy(GQLOperations.MakePrediction)
json_data["variables"] = { json_data["variables"] = {
"input": { "input": {
"eventID": event.event_id, "eventID": event.event_id,
"outcomeID": decision["id"], "outcomeID": decision["id"],
"points": decision["amount"], "points": decision["amount"],
"transactionID": "412118d3********79ac856", # How we can calculate this? "transactionID": token_hex(16),
} }
} }
return self.post_gql_request(json_data) return self.post_gql_request(json_data)

View File

@@ -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

View File

@@ -26,7 +26,6 @@ class TwitchWebSocket(WebSocketApp):
self.pending_topics = [] self.pending_topics = []
self.twitch = parent_pool.twitch self.twitch = parent_pool.twitch
self.browser = parent_pool.browser
self.streamers = parent_pool.streamers self.streamers = parent_pool.streamers
self.events_predictions = parent_pool.events_predictions self.events_predictions = parent_pool.events_predictions

View File

@@ -11,17 +11,16 @@ from TwitchChannelPointsMiner.classes.entities.Message import Message
from TwitchChannelPointsMiner.classes.entities.Raid import Raid from TwitchChannelPointsMiner.classes.entities.Raid import Raid
from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound from TwitchChannelPointsMiner.classes.Exceptions import TimeBasedDropNotFound
from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket from TwitchChannelPointsMiner.classes.TwitchWebSocket import TwitchWebSocket
from TwitchChannelPointsMiner.constants.twitch import WEBSOCKET from TwitchChannelPointsMiner.constants import WEBSOCKET
from TwitchChannelPointsMiner.utils import _millify, bet_condition, get_streamer_index from TwitchChannelPointsMiner.utils import _millify, get_streamer_index
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WebSocketsPool(object): class WebSocketsPool:
def __init__(self, twitch, browser, streamers, events_predictions): def __init__(self, twitch, streamers, events_predictions):
self.ws: list = [] self.ws = []
self.twitch = twitch self.twitch = twitch
self.browser = browser
self.streamers = streamers self.streamers = streamers
self.events_predictions = events_predictions self.events_predictions = events_predictions
@@ -214,29 +213,29 @@ class WebSocketsPool(object):
if ( if (
ws.streamers[streamer_index].is_online ws.streamers[streamer_index].is_online
and event.closing_bet_after(current_tmsp) > 0 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],)) if event.streamer.viewer_is_mod is True:
ws.events_predictions[event_id] = event logger.info(
start_after = event.closing_bet_after(current_tmsp) 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( place_bet_thread = threading.Timer(
start_after, start_after,
ws.browser.place_bet, ws.twitch.make_predictions,
(ws.events_predictions[event_id],), (ws.events_predictions[event_id],),
) )
place_bet_thread.daemon = True place_bet_thread.daemon = True
place_bet_thread.start() place_bet_thread.start()
logger.info( logger.info(
f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}", f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}",
extra={"emoji": ":alarm_clock:"}, extra={"emoji": ":alarm_clock:"},
) )
elif ( elif (
message.type == "event-updated" message.type == "event-updated"

View File

@@ -4,7 +4,7 @@ import time
from base64 import b64encode from base64 import b64encode
from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Settings import Settings
from TwitchChannelPointsMiner.constants.twitch import DROP_ID from TwitchChannelPointsMiner.constants import DROP_ID
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -4,7 +4,7 @@ import time
from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings
from TwitchChannelPointsMiner.classes.entities.Stream import Stream from TwitchChannelPointsMiner.classes.entities.Stream import Stream
from TwitchChannelPointsMiner.classes.Settings import Settings from TwitchChannelPointsMiner.classes.Settings import Settings
from TwitchChannelPointsMiner.constants.twitch import URL from TwitchChannelPointsMiner.constants import URL
from TwitchChannelPointsMiner.utils import _millify from TwitchChannelPointsMiner.utils import _millify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -5,6 +5,17 @@ WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1"
CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko"
DROP_ID = "c2542d6d-cd10-4532-919b-3d19f30a768b" 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: class GQLOperations:
url = "https://gql.twitch.tv/gql" url = "https://gql.twitch.tv/gql"

View File

@@ -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;
"""

View File

@@ -7,7 +7,7 @@ from random import randrange
from millify import millify from millify import millify
from TwitchChannelPointsMiner.constants.browser import USER_AGENTS from TwitchChannelPointsMiner.constants import USER_AGENTS
def _millify(input, precision=2): def _millify(input, precision=2):
@@ -51,24 +51,7 @@ def create_nonce(length=30) -> str:
return nonce return nonce
def bet_condition(twitch_browser, event, logger) -> bool: def get_user_agent(browser: str) -> str:
if twitch_browser.currently_is_betting is True:
logger.info(
f"Sorry, unable to start {event}, the browser is currently betting on another event!"
)
return False
elif twitch_browser.browser.current_url != "about:blank":
logger.info(
"Sorry, but the browser is not currently on 'about:blank' screen. Unable to start bet!"
)
return False
elif event.streamer.viewer_is_mod is True:
logger.info(f"Sorry, you are moderator of {event.streamer}, so you can't bet!")
return False
return True
def get_user_agent(browser) -> str:
try: try:
return USER_AGENTS[platform.system()][browser] return USER_AGENTS[platform.system()][browser]
except KeyError: except KeyError:

View File

@@ -5,7 +5,6 @@ from TwitchChannelPointsMiner import TwitchChannelPointsMiner
from TwitchChannelPointsMiner.logger import LoggerSettings from TwitchChannelPointsMiner.logger import LoggerSettings
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition
from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings
from TwitchChannelPointsMiner.classes.TwitchBrowser import Browser, BrowserSettings
twitch_miner = TwitchChannelPointsMiner( twitch_miner = TwitchChannelPointsMiner(
username="your-twitch-username", 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 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 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( streamer_settings=StreamerSettings(
make_predictions=True, # If you want to Bet / Make prediction make_predictions=True, # If you want to Bet / Make prediction
follow_raid=True, # Follow raid to obtain more points follow_raid=True, # Follow raid to obtain more points

View File

@@ -2,7 +2,6 @@ requests
websocket-client websocket-client
browser_cookie3 browser_cookie3
pillow pillow
selenium
python-dateutil python-dateutil
emoji emoji
millify millify