Merge pull request #50 from Tkd-Alex/off-selenium
Place the bet without Selenium
This commit is contained in:
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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**
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,6 +1,4 @@
|
|||||||
# Twitch Channel Points Miner - v2
|

|
||||||
|
|
||||||

|
|
||||||
<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.
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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__ = [
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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;
|
|
||||||
"""
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ requests
|
|||||||
websocket-client
|
websocket-client
|
||||||
browser_cookie3
|
browser_cookie3
|
||||||
pillow
|
pillow
|
||||||
selenium
|
|
||||||
python-dateutil
|
python-dateutil
|
||||||
emoji
|
emoji
|
||||||
millify
|
millify
|
||||||
|
|||||||
Reference in New Issue
Block a user