Merge branch 'master' into improvements-analytics
This commit is contained in:
13
README.md
13
README.md
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
11
example.py
11
example.py
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user