Skip to content

Commit

Permalink
Merge branch 'master' into 1v-smart_high_odds
Browse files Browse the repository at this point in the history
  • Loading branch information
Tkd-Alex committed Aug 21, 2021
2 parents cc0b901 + 76679e9 commit 1e9fea2
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/stale.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
daysUntilStale: 150

# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ from colorama import Fore
from TwitchChannelPointsMiner import TwitchChannelPointsMiner
from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette
from TwitchChannelPointsMiner.classes.Settings import Priority
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode
from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings

twitch_miner = TwitchChannelPointsMiner(
Expand Down Expand Up @@ -218,7 +218,9 @@ twitch_miner = TwitchChannelPointsMiner(
target_odd=3, # Target odd for SMART_HIGH_ODDS strategy
max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value
stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33
filter_condition=FilterCondition(
delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer
delay=6,
filter_condition=FilterCondition(
by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS]
where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value
value=800
Expand Down Expand Up @@ -279,6 +281,7 @@ You can watch only two streamers per time. With `priority` settings, you can sel
Available values are the following:
- `STREAK` - Catch the watch streak from all streamers
- `DROPS` - Claim all drops from streamers with drops tags enabled
- `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first)
- `ORDER` - Following the order of the list
- `POINTS_ASCENDING` - On top the streamers with the lowest points
- `POINTS_DESCEDING` - On top the streamers with the highest points
Expand Down Expand Up @@ -340,7 +343,6 @@ ColorPalette(
| `claim_drops` | bool | True | If this value is True, the script will increase the watch-time for the current game. With this, you can claim the drops from Twitch Inventory [#21](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/21) |
| `watch_streak` | bool | True | Choose if you want to change a priority for these streamers and try to catch the Watch Streak event [#11](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/11) |
| `bet` | BetSettings | | Rules to follow for the bet |
| `follow_raid` | bool | True | Choose if you want to follow raid +250 points |
### BetSettings
| Key | Type | Default | Description |
|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -350,7 +352,9 @@ ColorPalette(
| `target_odd` | float | 3 | Target odd for SMART_HIGH_ODDS strategy |
| `max_points` | int | 50000 | If the x percentage of your channel points is GT bet_max_points set this value |
| `stealth_mode` | bool | False | If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points [#33](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/33) |
| `join_chat` | bool | True | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) |
| `join_chat` | bool | True | Join IRC-Chat to appear online in chat and attempt to get StreamElements channel points and increase view-time [#47](https://github.com/Tkd-Alex/Twitch-Channel-Points-Miner-v2/issues/47) |
| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet |
| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value |

#### Bet strategy

Expand Down Expand Up @@ -398,6 +402,18 @@ Allowed values for `where` are: `GT, LT, GTE, LTE`
- If you want to place the bet ONLY if the highest bet is lower than 2000
`FilterCondition(by=OutcomeKeys.TOP_POINTS, where=Condition.LT, value=2000)`

### DelayMode

- **FROM_START**: Will wait `delay` seconds from when the bet was opened
- **FROM_END**: Will until there is `delay` seconds left to place the bet
- **PERCENTAGE**: Will place the bet when `delay` percent of the set timer is elapsed

Here's a concrete example. Let's suppose we have a bet that is opened with a timer of 10 minutes:

- **FROM_START** with `delay=20`: The bet will be placed 20s after the bet is opened
- **FROM_END** with `delay=20`: The bet will be placed 20s before the end of the bet (so 9mins 40s after the bet is opened)
- **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened)

## Analytics
We have recently introduced a little frontend where you can show with a chart you points trend. The script will spawn a Flask web-server on your machine where you can select binding address and port.
The chart provides some annotation to handle the prediction and watch strike events. Usually annotation are used to notice big increase / decrease of points. If you want to can disable annotations.
Expand Down
59 changes: 27 additions & 32 deletions TwitchChannelPointsMiner/TwitchChannelPointsMiner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import threading
import time
import uuid
from collections import OrderedDict
from datetime import datetime
from pathlib import Path

Expand Down Expand Up @@ -158,45 +157,41 @@ def run(self, streamers: list = [], blacklist: list = [], followers=False):
)
for username in followers_array:
if username not in streamers_dict and username not in blacklist:
streamers_name.append(username)
streamers_dict[username] = username.lower().strip()
else:
followers_array = []

streamers_name = list(
OrderedDict.fromkeys(streamers_name + followers_array)
)

logger.info(
f"Loading data for {len(streamers_name)} streamers. Please wait...",
extra={"emoji": ":nerd_face:"},
)
for username in streamers_name:
time.sleep(random.uniform(0.3, 0.7))
try:
streamer = (
streamers_dict[username]
if isinstance(streamers_dict[username], Streamer) is True
else Streamer(username)
)
streamer.channel_id = self.twitch.get_channel_id(username)
streamer.settings = set_default_settings(
streamer.settings, Settings.streamer_settings
)
streamer.settings.bet = set_default_settings(
streamer.settings.bet, Settings.streamer_settings.bet
)
if streamer.settings.join_chat is True:
streamer.irc_chat = ThreadChat(
self.username,
self.twitch.twitch_login.get_auth_token(),
streamer.username,
if username in streamers_name:
time.sleep(random.uniform(0.3, 0.7))
try:
streamer = (
streamers_dict[username]
if isinstance(streamers_dict[username], Streamer) is True
else Streamer(username)
)
streamer.channel_id = self.twitch.get_channel_id(username)
streamer.settings = set_default_settings(
streamer.settings, Settings.streamer_settings
)
streamer.settings.bet = set_default_settings(
streamer.settings.bet, Settings.streamer_settings.bet
)
if streamer.settings.join_chat is True:
streamer.irc_chat = ThreadChat(
self.username,
self.twitch.twitch_login.get_auth_token(),
streamer.username,
)
self.streamers.append(streamer)
except StreamerDoesNotExistException:
logger.info(
f"Streamer {username} does not exist",
extra={"emoji": ":cry:"},
)
self.streamers.append(streamer)
except StreamerDoesNotExistException:
logger.info(
f"Streamer {username} does not exist",
extra={"emoji": ":cry:"},
)

# Populate the streamers with default values.
# 1. Load channel points and auto-claim bonus
Expand Down
2 changes: 1 addition & 1 deletion TwitchChannelPointsMiner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
__version__ = "2.7.1"
__version__ = "2.7.2"
from .TwitchChannelPointsMiner import TwitchChannelPointsMiner

__all__ = [
Expand Down
1 change: 1 addition & 0 deletions TwitchChannelPointsMiner/classes/Settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Priority(Enum):
ORDER = auto()
STREAK = auto()
DROPS = auto()
SUBSCRIBED = auto()
POINTS_ASCENDING = auto()
POINTS_DESCEDING = auto()

Expand Down
38 changes: 37 additions & 1 deletion TwitchChannelPointsMiner/classes/Twitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,19 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3):
if len(streamers_watching) == 2:
break

elif prior == Priority.SUBSCRIBED and len(streamers_watching) < 2:
streamers_with_multiplier = [
index
for index in streamers_index
if streamers[index].viewer_has_points_multiplier()
]
streamers_with_multiplier = sorted(
streamers_with_multiplier,
key=lambda x: streamers[x].total_points_multiplier(),
reverse=True,
)
streamers_watching += streamers_with_multiplier[:2]

"""
Twitch has a limit - you can't watch more than 2 channels at one time.
We take the first two streamers from the list as they have the highest priority (based on order or WatchStreak).
Expand Down Expand Up @@ -386,6 +399,7 @@ def load_channel_points_context(self, streamer):
channel = response["data"]["community"]["channel"]
community_points = channel["self"]["communityPoints"]
streamer.channel_points = community_points["balance"]
streamer.activeMultipliers = community_points["activeMultipliers"]

if community_points["availableClaim"] is not None:
self.claim_bonus(streamer, community_points["availableClaim"]["id"])
Expand Down Expand Up @@ -437,7 +451,29 @@ def make_predictions(self, event):
"transactionID": token_hex(16),
}
}
return self.post_gql_request(json_data)
response = self.post_gql_request(json_data)
if (
"data" in response
and "makePrediction" in response["data"]
and "error" in response["data"]["makePrediction"]
and response["data"]["makePrediction"]["error"] is not None
):
error_code = response["data"]["makePrediction"]["error"]["code"]
logger.error(
f"Failed to place bet, error: {error_code}",
extra={
"emoji": ":four_leaf_clover:",
"color": Settings.logger.color_palette.BET_FAILED,
},
)
else:
logger.info(
f"Bet won't be placed as the amount {_millify(decision['amount'])} is less than the minimum required 10",
extra={
"emoji": ":four_leaf_clover:",
"color": Settings.logger.color_palette.BET_GENERAL,
},
)
else:
logger.info(
f"Oh no! The event is not active anymore! Current status: {event.status}",
Expand Down
2 changes: 1 addition & 1 deletion TwitchChannelPointsMiner/classes/TwitchLogin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


class TwitchLogin(object):
__self__ = [
__slots__ = [
"client_id",
"token",
"login_check_result",
Expand Down
6 changes: 4 additions & 2 deletions TwitchChannelPointsMiner/classes/WebSocketsPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def on_error(ws, error):
logger.error(f"#{ws.index} - WebSocket error: {error}")

@staticmethod
def on_close(ws):
def on_close(ws, close_status_code, close_reason):
logger.info(f"#{ws.index} - WebSocket closed")
# On close please reconnect automatically
WebSocketsPool.handle_reconnection(ws)
Expand Down Expand Up @@ -247,7 +247,9 @@ def on_message(ws, message):
event_dict["prediction_window_seconds"]
)
# Reduce prediction window by 3/6s - Collect more accurate data for decision
prediction_window_seconds -= random.uniform(3, 6)
prediction_window_seconds = ws.streamers[
streamer_index
].get_prediction_window(prediction_window_seconds)
event = EventPrediction(
ws.streamers[streamer_index],
event_id,
Expand Down
17 changes: 17 additions & 0 deletions TwitchChannelPointsMiner/classes/entities/Bet.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ class OutcomeKeys(object):
DECISION_POINTS = "decision_points"


class DelayMode(Enum):
FROM_START = auto()
FROM_END = auto()
PERCENTAGE = auto()

def __str__(self):
return self.name


class FilterCondition(object):
__slots__ = [
"by",
Expand All @@ -68,6 +77,8 @@ class BetSettings(object):
"only_doubt",
"stealth_mode",
"filter_condition",
"delay",
"delay_mode",
]

def __init__(
Expand All @@ -80,6 +91,8 @@ def __init__(
only_doubt: bool = None,
stealth_mode: bool = None,
filter_condition: FilterCondition = None,
delay: float = None,
delay_mode: DelayMode = None,
):
self.strategy = strategy
self.percentage = percentage
Expand All @@ -89,6 +102,8 @@ def __init__(
self.only_doubt = only_doubt
self.stealth_mode = stealth_mode
self.filter_condition = filter_condition
self.delay = delay
self.delay_mode = delay_mode

def default(self):
self.strategy = self.strategy if not None else Strategy.SMART
Expand All @@ -98,6 +113,8 @@ def default(self):
self.target_odd = self.target_odd if not None else 3
self.only_doubt = self.only_doubt if not None else False
self.stealth_mode = self.stealth_mode if not None else False
self.delay = self.delay if not None else 6
self.delay_mode = self.delay_mode if not None else DelayMode.FROM_END

def __repr__(self):
return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, stealth_mode={self.stealth_mode})"
Expand Down
31 changes: 30 additions & 1 deletion TwitchChannelPointsMiner/classes/entities/Streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from threading import Lock

from TwitchChannelPointsMiner.classes.Chat import ThreadChat
from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings
from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings, DelayMode
from TwitchChannelPointsMiner.classes.entities.Stream import Stream
from TwitchChannelPointsMiner.classes.Settings import Settings
from TwitchChannelPointsMiner.constants import URL
Expand Down Expand Up @@ -70,6 +70,7 @@ class Streamer(object):
"channel_points",
"minute_watched_requests",
"viewer_is_mod",
"activeMultipliers",
"irc_chat",
"stream",
"raid",
Expand All @@ -89,6 +90,7 @@ def __init__(self, username, settings=None):
self.channel_points = 0
self.minute_watched_requests = None
self.viewer_is_mod = False
self.activeMultipliers = None
self.irc_chat = None

self.stream = Stream()
Expand Down Expand Up @@ -168,6 +170,33 @@ def drops_condition(self):
and self.stream.campaigns_ids != []
)

def viewer_has_points_multiplier(self):
return self.activeMultipliers is not None and len(self.activeMultipliers) > 0

def total_points_multiplier(self):
return (
sum(
map(
lambda x: x["factor"],
self.activeMultipliers,
),
)
if self.activeMultipliers is not None
else 0
)

def get_prediction_window(self, prediction_window_seconds):
delay_mode = self.settings.bet.delay_mode
delay = self.settings.bet.delay
if delay_mode == DelayMode.FROM_START:
return min(delay, prediction_window_seconds)
elif delay_mode == DelayMode.FROM_END:
return max(prediction_window_seconds - delay, 0)
elif delay_mode == DelayMode.PERCENTAGE:
return prediction_window_seconds * delay
else:
return prediction_window_seconds

# === ANALYTICS === #
def persistent_annotations(self, event_type, event_text):
event_type = event_type.upper()
Expand Down
13 changes: 10 additions & 3 deletions TwitchChannelPointsMiner/logger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
import platform
from datetime import datetime
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path

import emoji
Expand Down Expand Up @@ -144,9 +144,16 @@ def configure_loggers(username, settings):
Path(logs_path).mkdir(parents=True, exist_ok=True)
logs_file = os.path.join(
logs_path,
f"{username}.{datetime.now().strftime('%Y%m%d-%H%M%S')}.log",
f"{username}.log",
)
file_handler = TimedRotatingFileHandler(
logs_file,
when="D",
interval=1,
backupCount=7,
encoding="utf-8",
delay=False,
)
file_handler = logging.FileHandler(logs_file, "w", "utf-8")
file_handler.setFormatter(
logging.Formatter(
fmt="%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s]: %(message)s",
Expand Down
Loading

0 comments on commit 1e9fea2

Please sign in to comment.