Skip to content

Commit 1e9fea2

Browse files
committed
Merge branch 'master' into 1v-smart_high_odds
2 parents cc0b901 + 76679e9 commit 1e9fea2

File tree

12 files changed

+160
-57
lines changed

12 files changed

+160
-57
lines changed

.github/stale.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Number of days of inactivity before an issue becomes stale
2-
daysUntilStale: 60
2+
daysUntilStale: 150
33

44
# Number of days of inactivity before a stale issue is closed
55
daysUntilClose: 7

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ from colorama import Fore
180180
from TwitchChannelPointsMiner import TwitchChannelPointsMiner
181181
from TwitchChannelPointsMiner.logger import LoggerSettings, ColorPalette
182182
from TwitchChannelPointsMiner.classes.Settings import Priority
183-
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition
183+
from TwitchChannelPointsMiner.classes.entities.Bet import Strategy, BetSettings, Condition, OutcomeKeys, FilterCondition, DelayMode
184184
from TwitchChannelPointsMiner.classes.entities.Streamer import Streamer, StreamerSettings
185185

186186
twitch_miner = TwitchChannelPointsMiner(
@@ -218,7 +218,9 @@ twitch_miner = TwitchChannelPointsMiner(
218218
target_odd=3, # Target odd for SMART_HIGH_ODDS strategy
219219
max_points=50000, # If the x percentage of your channel points is gt bet_max_points set this value
220220
stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points #33
221-
filter_condition=FilterCondition(
221+
delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer
222+
delay=6,
223+
filter_condition=FilterCondition(
222224
by=OutcomeKeys.TOTAL_USERS, # Where apply the filter. Allowed [PERCENTAGE_USERS, ODDS_PERCENTAGE, ODDS, TOP_POINTS, TOTAL_USERS, TOTAL_POINTS]
223225
where=Condition.LTE, # 'by' must be [GT, LT, GTE, LTE] than value
224226
value=800
@@ -279,6 +281,7 @@ You can watch only two streamers per time. With `priority` settings, you can sel
279281
Available values are the following:
280282
- `STREAK` - Catch the watch streak from all streamers
281283
- `DROPS` - Claim all drops from streamers with drops tags enabled
284+
- `SUBSCRIBED` - Prioritize streamers you're subscribed to (higher subscription tiers are mined first)
282285
- `ORDER` - Following the order of the list
283286
- `POINTS_ASCENDING` - On top the streamers with the lowest points
284287
- `POINTS_DESCEDING` - On top the streamers with the highest points
@@ -340,7 +343,6 @@ ColorPalette(
340343
| `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) |
341344
| `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) |
342345
| `bet` | BetSettings | | Rules to follow for the bet |
343-
| `follow_raid` | bool | True | Choose if you want to follow raid +250 points |
344346
### BetSettings
345347
| Key | Type | Default | Description |
346348
|-------------------- |----------------- |--------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -350,7 +352,9 @@ ColorPalette(
350352
| `target_odd` | float | 3 | Target odd for SMART_HIGH_ODDS strategy |
351353
| `max_points` | int | 50000 | If the x percentage of your channel points is GT bet_max_points set this value |
352354
| `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) |
353-
| `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) |
355+
| `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) |
356+
| `delay_mode` | DelayMode | FROM_END | Define how is calculating the waiting time before placing a bet |
357+
| `delay` | float | 6 | Value to be used to calculate bet delay depending on `delay_mode` value |
354358

355359
#### Bet strategy
356360

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

405+
### DelayMode
406+
407+
- **FROM_START**: Will wait `delay` seconds from when the bet was opened
408+
- **FROM_END**: Will until there is `delay` seconds left to place the bet
409+
- **PERCENTAGE**: Will place the bet when `delay` percent of the set timer is elapsed
410+
411+
Here's a concrete example. Let's suppose we have a bet that is opened with a timer of 10 minutes:
412+
413+
- **FROM_START** with `delay=20`: The bet will be placed 20s after the bet is opened
414+
- **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)
415+
- **PERCENTAGE** with `delay=0.2`: The bet will be placed when the timer went down by 20% (so 2mins after the bet is opened)
416+
401417
## Analytics
402418
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.
403419
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.

TwitchChannelPointsMiner/TwitchChannelPointsMiner.py

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import threading
99
import time
1010
import uuid
11-
from collections import OrderedDict
1211
from datetime import datetime
1312
from pathlib import Path
1413

@@ -158,45 +157,41 @@ def run(self, streamers: list = [], blacklist: list = [], followers=False):
158157
)
159158
for username in followers_array:
160159
if username not in streamers_dict and username not in blacklist:
160+
streamers_name.append(username)
161161
streamers_dict[username] = username.lower().strip()
162-
else:
163-
followers_array = []
164-
165-
streamers_name = list(
166-
OrderedDict.fromkeys(streamers_name + followers_array)
167-
)
168162

169163
logger.info(
170164
f"Loading data for {len(streamers_name)} streamers. Please wait...",
171165
extra={"emoji": ":nerd_face:"},
172166
)
173167
for username in streamers_name:
174-
time.sleep(random.uniform(0.3, 0.7))
175-
try:
176-
streamer = (
177-
streamers_dict[username]
178-
if isinstance(streamers_dict[username], Streamer) is True
179-
else Streamer(username)
180-
)
181-
streamer.channel_id = self.twitch.get_channel_id(username)
182-
streamer.settings = set_default_settings(
183-
streamer.settings, Settings.streamer_settings
184-
)
185-
streamer.settings.bet = set_default_settings(
186-
streamer.settings.bet, Settings.streamer_settings.bet
187-
)
188-
if streamer.settings.join_chat is True:
189-
streamer.irc_chat = ThreadChat(
190-
self.username,
191-
self.twitch.twitch_login.get_auth_token(),
192-
streamer.username,
168+
if username in streamers_name:
169+
time.sleep(random.uniform(0.3, 0.7))
170+
try:
171+
streamer = (
172+
streamers_dict[username]
173+
if isinstance(streamers_dict[username], Streamer) is True
174+
else Streamer(username)
175+
)
176+
streamer.channel_id = self.twitch.get_channel_id(username)
177+
streamer.settings = set_default_settings(
178+
streamer.settings, Settings.streamer_settings
179+
)
180+
streamer.settings.bet = set_default_settings(
181+
streamer.settings.bet, Settings.streamer_settings.bet
182+
)
183+
if streamer.settings.join_chat is True:
184+
streamer.irc_chat = ThreadChat(
185+
self.username,
186+
self.twitch.twitch_login.get_auth_token(),
187+
streamer.username,
188+
)
189+
self.streamers.append(streamer)
190+
except StreamerDoesNotExistException:
191+
logger.info(
192+
f"Streamer {username} does not exist",
193+
extra={"emoji": ":cry:"},
193194
)
194-
self.streamers.append(streamer)
195-
except StreamerDoesNotExistException:
196-
logger.info(
197-
f"Streamer {username} does not exist",
198-
extra={"emoji": ":cry:"},
199-
)
200195

201196
# Populate the streamers with default values.
202197
# 1. Load channel points and auto-claim bonus

TwitchChannelPointsMiner/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "2.7.1"
2+
__version__ = "2.7.2"
33
from .TwitchChannelPointsMiner import TwitchChannelPointsMiner
44

55
__all__ = [

TwitchChannelPointsMiner/classes/Settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class Priority(Enum):
55
ORDER = auto()
66
STREAK = auto()
77
DROPS = auto()
8+
SUBSCRIBED = auto()
89
POINTS_ASCENDING = auto()
910
POINTS_DESCEDING = auto()
1011

TwitchChannelPointsMiner/classes/Twitch.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,19 @@ def send_minute_watched_events(self, streamers, priority, chunk_size=3):
314314
if len(streamers_watching) == 2:
315315
break
316316

317+
elif prior == Priority.SUBSCRIBED and len(streamers_watching) < 2:
318+
streamers_with_multiplier = [
319+
index
320+
for index in streamers_index
321+
if streamers[index].viewer_has_points_multiplier()
322+
]
323+
streamers_with_multiplier = sorted(
324+
streamers_with_multiplier,
325+
key=lambda x: streamers[x].total_points_multiplier(),
326+
reverse=True,
327+
)
328+
streamers_watching += streamers_with_multiplier[:2]
329+
317330
"""
318331
Twitch has a limit - you can't watch more than 2 channels at one time.
319332
We take the first two streamers from the list as they have the highest priority (based on order or WatchStreak).
@@ -386,6 +399,7 @@ def load_channel_points_context(self, streamer):
386399
channel = response["data"]["community"]["channel"]
387400
community_points = channel["self"]["communityPoints"]
388401
streamer.channel_points = community_points["balance"]
402+
streamer.activeMultipliers = community_points["activeMultipliers"]
389403

390404
if community_points["availableClaim"] is not None:
391405
self.claim_bonus(streamer, community_points["availableClaim"]["id"])
@@ -437,7 +451,29 @@ def make_predictions(self, event):
437451
"transactionID": token_hex(16),
438452
}
439453
}
440-
return self.post_gql_request(json_data)
454+
response = self.post_gql_request(json_data)
455+
if (
456+
"data" in response
457+
and "makePrediction" in response["data"]
458+
and "error" in response["data"]["makePrediction"]
459+
and response["data"]["makePrediction"]["error"] is not None
460+
):
461+
error_code = response["data"]["makePrediction"]["error"]["code"]
462+
logger.error(
463+
f"Failed to place bet, error: {error_code}",
464+
extra={
465+
"emoji": ":four_leaf_clover:",
466+
"color": Settings.logger.color_palette.BET_FAILED,
467+
},
468+
)
469+
else:
470+
logger.info(
471+
f"Bet won't be placed as the amount {_millify(decision['amount'])} is less than the minimum required 10",
472+
extra={
473+
"emoji": ":four_leaf_clover:",
474+
"color": Settings.logger.color_palette.BET_GENERAL,
475+
},
476+
)
441477
else:
442478
logger.info(
443479
f"Oh no! The event is not active anymore! Current status: {event.status}",

TwitchChannelPointsMiner/classes/TwitchLogin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
class TwitchLogin(object):
22-
__self__ = [
22+
__slots__ = [
2323
"client_id",
2424
"token",
2525
"login_check_result",

TwitchChannelPointsMiner/classes/WebSocketsPool.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def on_error(ws, error):
110110
logger.error(f"#{ws.index} - WebSocket error: {error}")
111111

112112
@staticmethod
113-
def on_close(ws):
113+
def on_close(ws, close_status_code, close_reason):
114114
logger.info(f"#{ws.index} - WebSocket closed")
115115
# On close please reconnect automatically
116116
WebSocketsPool.handle_reconnection(ws)
@@ -247,7 +247,9 @@ def on_message(ws, message):
247247
event_dict["prediction_window_seconds"]
248248
)
249249
# Reduce prediction window by 3/6s - Collect more accurate data for decision
250-
prediction_window_seconds -= random.uniform(3, 6)
250+
prediction_window_seconds = ws.streamers[
251+
streamer_index
252+
].get_prediction_window(prediction_window_seconds)
251253
event = EventPrediction(
252254
ws.streamers[streamer_index],
253255
event_id,

TwitchChannelPointsMiner/classes/entities/Bet.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ class OutcomeKeys(object):
4242
DECISION_POINTS = "decision_points"
4343

4444

45+
class DelayMode(Enum):
46+
FROM_START = auto()
47+
FROM_END = auto()
48+
PERCENTAGE = auto()
49+
50+
def __str__(self):
51+
return self.name
52+
53+
4554
class FilterCondition(object):
4655
__slots__ = [
4756
"by",
@@ -68,6 +77,8 @@ class BetSettings(object):
6877
"only_doubt",
6978
"stealth_mode",
7079
"filter_condition",
80+
"delay",
81+
"delay_mode",
7182
]
7283

7384
def __init__(
@@ -80,6 +91,8 @@ def __init__(
8091
only_doubt: bool = None,
8192
stealth_mode: bool = None,
8293
filter_condition: FilterCondition = None,
94+
delay: float = None,
95+
delay_mode: DelayMode = None,
8396
):
8497
self.strategy = strategy
8598
self.percentage = percentage
@@ -89,6 +102,8 @@ def __init__(
89102
self.only_doubt = only_doubt
90103
self.stealth_mode = stealth_mode
91104
self.filter_condition = filter_condition
105+
self.delay = delay
106+
self.delay_mode = delay_mode
92107

93108
def default(self):
94109
self.strategy = self.strategy if not None else Strategy.SMART
@@ -98,6 +113,8 @@ def default(self):
98113
self.target_odd = self.target_odd if not None else 3
99114
self.only_doubt = self.only_doubt if not None else False
100115
self.stealth_mode = self.stealth_mode if not None else False
116+
self.delay = self.delay if not None else 6
117+
self.delay_mode = self.delay_mode if not None else DelayMode.FROM_END
101118

102119
def __repr__(self):
103120
return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, stealth_mode={self.stealth_mode})"

TwitchChannelPointsMiner/classes/entities/Streamer.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from threading import Lock
77

88
from TwitchChannelPointsMiner.classes.Chat import ThreadChat
9-
from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings
9+
from TwitchChannelPointsMiner.classes.entities.Bet import BetSettings, DelayMode
1010
from TwitchChannelPointsMiner.classes.entities.Stream import Stream
1111
from TwitchChannelPointsMiner.classes.Settings import Settings
1212
from TwitchChannelPointsMiner.constants import URL
@@ -70,6 +70,7 @@ class Streamer(object):
7070
"channel_points",
7171
"minute_watched_requests",
7272
"viewer_is_mod",
73+
"activeMultipliers",
7374
"irc_chat",
7475
"stream",
7576
"raid",
@@ -89,6 +90,7 @@ def __init__(self, username, settings=None):
8990
self.channel_points = 0
9091
self.minute_watched_requests = None
9192
self.viewer_is_mod = False
93+
self.activeMultipliers = None
9294
self.irc_chat = None
9395

9496
self.stream = Stream()
@@ -168,6 +170,33 @@ def drops_condition(self):
168170
and self.stream.campaigns_ids != []
169171
)
170172

173+
def viewer_has_points_multiplier(self):
174+
return self.activeMultipliers is not None and len(self.activeMultipliers) > 0
175+
176+
def total_points_multiplier(self):
177+
return (
178+
sum(
179+
map(
180+
lambda x: x["factor"],
181+
self.activeMultipliers,
182+
),
183+
)
184+
if self.activeMultipliers is not None
185+
else 0
186+
)
187+
188+
def get_prediction_window(self, prediction_window_seconds):
189+
delay_mode = self.settings.bet.delay_mode
190+
delay = self.settings.bet.delay
191+
if delay_mode == DelayMode.FROM_START:
192+
return min(delay, prediction_window_seconds)
193+
elif delay_mode == DelayMode.FROM_END:
194+
return max(prediction_window_seconds - delay, 0)
195+
elif delay_mode == DelayMode.PERCENTAGE:
196+
return prediction_window_seconds * delay
197+
else:
198+
return prediction_window_seconds
199+
171200
# === ANALYTICS === #
172201
def persistent_annotations(self, event_type, event_text):
173202
event_type = event_type.upper()

0 commit comments

Comments
 (0)