Merge branch 'master' into improvements-analytics

This commit is contained in:
Alessandro Maggio
2021-10-05 15:33:26 +02:00
10 changed files with 143 additions and 80 deletions

View File

@@ -216,12 +216,13 @@ twitch_miner = TwitchChannelPointsMiner(
percentage=5, # Place the x% of your channel points
percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy)
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
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
stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33
delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer
delay=6,
minimum_points=2000, # Place the bet only if we have at least 20k points. Issue #113
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
)
)

View File

@@ -248,10 +248,11 @@ class TwitchChannelPointsMiner:
)
# Subscribe to community-points-user. Get update for points spent or gains
user_id = self.twitch.twitch_login.get_user_id()
self.ws_pool.submit(
PubsubTopic(
"community-points-user-v1",
user_id=self.twitch.twitch_login.get_user_id(),
user_id=user_id,
)
)
@@ -260,7 +261,7 @@ class TwitchChannelPointsMiner:
self.ws_pool.submit(
PubsubTopic(
"predictions-user-v1",
user_id=self.twitch.twitch_login.get_user_id(),
user_id=user_id,
)
)
@@ -311,7 +312,8 @@ class TwitchChannelPointsMiner:
streamer.irc_chat.join()
self.running = self.twitch.running = False
self.ws_pool.end()
if self.ws_pool is not None:
self.ws_pool.end()
if self.minute_watcher_thread is not None:
self.minute_watcher_thread.join()

View File

@@ -23,7 +23,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import (
)
from TwitchChannelPointsMiner.classes.Settings import Priority, Settings
from TwitchChannelPointsMiner.classes.TwitchLogin import TwitchLogin
from TwitchChannelPointsMiner.constants import API, CLIENT_ID, GQLOperations
from TwitchChannelPointsMiner.constants import CLIENT_ID, GQLOperations
from TwitchChannelPointsMiner.utils import (
_millify,
create_chunks,
@@ -143,33 +143,34 @@ class Twitch(object):
streamer.set_offline()
def get_channel_id(self, streamer_username):
json_response = self.__do_helix_request(f"/users?login={streamer_username}")
if "data" not in json_response:
json_data = copy.deepcopy(GQLOperations.ReportMenuItem)
json_data["variables"] = {"channelLogin": streamer_username}
json_response = self.post_gql_request(json_data)
if (
"data" not in json_response
or "user" not in json_response["data"]
or json_response["data"]["user"] is None
):
raise StreamerDoesNotExistException
else:
data = json_response["data"]
if len(data) >= 1:
return data[0]["id"]
else:
raise StreamerDoesNotExistException
return json_response["data"]["user"]["id"]
def get_followers(self, first=100):
followers = []
pagination = {}
while 1:
query = f"/users/follows?from_id={self.twitch_login.get_user_id()}&first={first}"
if pagination != {}:
query += f"&after={pagination['cursor']}"
json_response = self.__do_helix_request(query)
pagination = json_response["pagination"]
followers += [fw["to_login"].lower() for fw in json_response["data"]]
time.sleep(random.uniform(0.3, 0.7))
if pagination == {}:
break
return followers
def get_followers(self):
json_data = copy.deepcopy(GQLOperations.PersonalSections)
json_response = self.post_gql_request(json_data)
try:
if (
"data" in json_response
and "personalSections" in json_response["data"]
and json_response["data"]["personalSections"] != []
):
return [
fw["user"]["login"]
for fw in json_response["data"]["personalSections"][0]["items"]
if fw["user"] is not None
]
except KeyError:
return []
def update_raid(self, streamer, raid):
if streamer.raid != raid:
@@ -211,14 +212,6 @@ class Twitch(object):
)
self.__chuncked_sleep(random_sleep * 60, chunk_size=chunk_size)
def __do_helix_request(self, query, response_as_json=True):
url = f"{API}/helix/{query.strip('/')}"
response = self.twitch_login.session.get(url)
logger.debug(
f"Query: {query}, Status code: {response.status_code}, Content: {response.json()}"
)
return response.json() if response_as_json is True else response
def post_gql_request(self, json_data):
try:
response = requests.post(
@@ -362,14 +355,12 @@ class Twitch(object):
drop.has_preconditions_met is not False
and drop.is_printable is True
):
# print("=" * 125)
logger.info(
f"{streamers[index]} is streaming {streamers[index].stream}"
)
logger.info(f"Campaign: {campaign}")
logger.info(f"Drop: {drop}")
logger.info(f"{drop.progress_bar()}")
# print("=" * 125)
except requests.exceptions.ConnectionError as e:
logger.error(f"Error while trying to send minute watched: {e}")
@@ -541,7 +532,9 @@ class Twitch(object):
}
response = self.post_gql_request(json_data)
result += list(map(lambda x: x["data"]["user"]["dropCampaign"], response))
for r in response:
if r["data"]["user"] is not None:
result.append(r["data"]["user"]["dropCampaign"])
return result
def __sync_campaigns(self, campaigns):

View File

@@ -2,6 +2,7 @@
# Original Copyright (c) 2020 Rodney
# The MIT License (MIT)
import copy
import getpass
import logging
import os
@@ -14,6 +15,7 @@ from TwitchChannelPointsMiner.classes.Exceptions import (
BadCredentialsException,
WrongCookiesException,
)
from TwitchChannelPointsMiner.constants import GQLOperations
logger = logging.getLogger(__name__)
@@ -177,14 +179,7 @@ class TwitchLogin(object):
if self.token is None:
return False
response = self.session.get(
f"https://api.twitch.tv/helix/users?login={self.username}"
)
response = response.json()
if "data" in response:
self.login_check_result = True
self.user_id = response["data"][0]["id"]
self.login_check_result = self.__set_user_id()
return self.login_check_result
def save_cookies(self, cookies_file):
@@ -203,6 +198,7 @@ class TwitchLogin(object):
if cookie["name"] == key:
if cookie["value"] is not None:
return cookie["value"]
return None
def load_cookies(self, cookies_file):
if os.path.isfile(cookies_file):
@@ -211,7 +207,30 @@ class TwitchLogin(object):
raise WrongCookiesException("There must be a cookies file!")
def get_user_id(self):
return int(self.get_cookie_value("persistent").split("%")[0])
persistent = self.get_cookie_value("persistent")
user_id = (
int(persistent.split("%")[0]) if persistent is not None else self.user_id
)
if user_id is None:
if self.__set_user_id() is True:
return self.user_id
return user_id
def __set_user_id(self):
json_data = copy.deepcopy(GQLOperations.ReportMenuItem)
json_data["variables"] = {"channelLogin": self.username}
response = self.session.post(GQLOperations.url, json=json_data)
if response.status_code == 200:
json_response = response.json()
if (
"data" in json_response
and "user" in json_response["data"]
and json_response["data"]["user"]["id"] is not None
):
self.user_id = json_response["data"]["user"]["id"]
return True
return False
def get_auth_token(self):
return self.get_cookie_value("auth-token")

View File

@@ -43,7 +43,6 @@ class TwitchWebSocket(WebSocketApp):
data = {"topics": [str(topic)]}
if topic.is_user_topic() and auth_token is not None:
data["auth_token"] = auth_token
nonce = create_nonce()
self.send({"type": "LISTEN", "nonce": nonce, "data": data})

View File

@@ -142,7 +142,6 @@ class WebSocketsPool:
# Why not create a new ws on the same array index? Let's try.
self = ws.parent_pool
self.ws[ws.index] = self.__new(ws.index) # Create a new connection.
# self.ws[ws.index].topics = ws.topics
self.__start(ws.index) # Start a new thread.
time.sleep(30)
@@ -263,24 +262,41 @@ class WebSocketsPool:
ws.streamers[streamer_index].is_online
and event.closing_bet_after(current_tmsp) > 0
):
ws.events_predictions[event_id] = event
start_after = event.closing_bet_after(current_tmsp)
streamer = ws.streamers[streamer_index]
bet_settings = streamer.settings.bet
if (
bet_settings.minimum_points is None
or streamer.channel_points
> bet_settings.minimum_points
):
ws.events_predictions[event_id] = event
start_after = event.closing_bet_after(
current_tmsp
)
place_bet_thread = Timer(
start_after,
ws.twitch.make_predictions,
(ws.events_predictions[event_id],),
)
place_bet_thread.daemon = True
place_bet_thread.start()
place_bet_thread = Timer(
start_after,
ws.twitch.make_predictions,
(ws.events_predictions[event_id],),
)
place_bet_thread.daemon = True
place_bet_thread.start()
logger.info(
f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}",
extra={
"emoji": ":alarm_clock:",
"color": Settings.logger.color_palette.BET_START,
},
)
logger.info(
f"Place the bet after: {start_after}s for: {ws.events_predictions[event_id]}",
extra={
"emoji": ":alarm_clock:",
"color": Settings.logger.color_palette.BET_START,
},
)
else:
logger.info(
f"{streamer} have only {streamer.channel_points} channel points and the minimum for bet is: {bet_settings.minimum_points}",
extra={
"emoji": ":pushpin:",
"color": Settings.logger.color_palette.BET_FILTERS,
},
)
elif (
message.type == "event-updated"

View File

@@ -72,6 +72,7 @@ class BetSettings(object):
"percentage",
"percentage_gap",
"max_points",
"minimum_points",
"stealth_mode",
"filter_condition",
"delay",
@@ -84,6 +85,7 @@ class BetSettings(object):
percentage: int = None,
percentage_gap: int = None,
max_points: int = None,
minimum_points: int = None,
stealth_mode: bool = None,
filter_condition: FilterCondition = None,
delay: float = None,
@@ -93,6 +95,7 @@ class BetSettings(object):
self.percentage = percentage
self.percentage_gap = percentage_gap
self.max_points = max_points
self.minimum_points = minimum_points
self.stealth_mode = stealth_mode
self.filter_condition = filter_condition
self.delay = delay
@@ -103,12 +106,13 @@ class BetSettings(object):
self.percentage = self.percentage if not None else 5
self.percentage_gap = self.percentage_gap if not None else 20
self.max_points = self.max_points if not None else 50000
self.minimum_points = self.minimum_points if not None else 0
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})"
return f"BetSettings(strategy={self.strategy}, percentage={self.percentage}, percentage_gap={self.percentage_gap}, max_points={self.max_points}, minimum_points={self.minimum_points}, stealth_mode={self.stealth_mode})"
class Bet(object):

View File

@@ -1,6 +1,5 @@
# Twitch endpoints
URL = "https://www.twitch.tv"
API = "https://api.twitch.tv"
IRC = "irc.chat.twitch.tv"
IRC_PORT = 6667
WEBSOCKET = "wss://pubsub-edge.twitch.tv/v1"
@@ -118,7 +117,7 @@ class GQLOperations:
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "14b5e8a50777165cfc3971e1d93b4758613fe1c817d5542c398dce70b7a45c05", # "7da6078b1bfa2f0a4dd061cb47bdcd1ffddf31cccadd966ec192e4cd06666e2b",
"sha256Hash": "14b5e8a50777165cfc3971e1d93b4758613fe1c817d5542c398dce70b7a45c05",
}
},
}
@@ -131,3 +130,30 @@ class GQLOperations:
}
},
}
ReportMenuItem = { # Use for replace https://api.twitch.tv/helix/users?login={self.username}
"operationName": "ReportMenuItem",
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "8f3628981255345ca5e5453dfd844efffb01d6413a9931498836e6268692a30c",
}
},
}
PersonalSections = {
"operationName": "PersonalSections",
"variables": {
"input": {
"sectionInputs": ["FOLLOWED_SECTION"],
"recommendationContext": {"platform": "web"},
},
"channelLogin": None,
"withChannelUser": False,
"creatorAnniversariesExperimentEnabled": False,
},
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "9fbdfb00156f754c26bde81eb47436dee146655c92682328457037da1a48ed39",
}
},
}

View File

@@ -5,7 +5,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(
@@ -41,10 +41,13 @@ twitch_miner = TwitchChannelPointsMiner(
percentage=5, # Place the x% of your channel points
percentage_gap=20, # Gap difference between outcomesA and outcomesB (for SMART stragegy)
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
stealth_mode=True, # If the calculated amount of channel points is GT the highest bet, place the highest value minus 1-2 points Issue #33
delay_mode=DelayMode.FROM_END, # When placing a bet, we will wait until `delay` seconds before the end of the timer
delay=6,
minimum_points=2000, # Place the bet only if we have at least 20k points. Issue #113
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
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
)
)

View File

@@ -1,7 +1,7 @@
requests==2.25.1
websocket-client==1.0.1
browser_cookie3==0.12.1
pillow==8.2.0
pillow==8.3.2
python-dateutil==2.8.1
emoji==1.2.0
millify==0.1.1