def on_message(ws, message): logger.debug(f"#{ws.index} - Received: {message.strip()}") response = json.loads(message) if response["type"] == "MESSAGE": # We should create a Message class ... message = Message(response["data"]) # If we have more than one PubSub connection, messages may be duplicated # Check the concatenation between message_type.top.channel_id if (ws.last_message_type_channel is not None and ws.last_message_timestamp is not None and ws.last_message_timestamp == message.timestamp and ws.last_message_type_channel == message.identifier): return ws.last_message_timestamp = message.timestamp ws.last_message_type_channel = message.identifier streamer_index = get_streamer_index(ws.streamers, message.channel_id) if streamer_index != -1: try: if message.topic == "community-points-user-v1": if message.type in ["points-earned", "points-spent"]: balance = message.data["balance"]["balance"] ws.streamers[ streamer_index].channel_points = balance ws.streamers[streamer_index].persistent_series( event_type=message.data["point_gain"] ["reason_code"] if message.type == "points-earned" else "Spent") if message.type == "points-earned": earned = message.data["point_gain"]["total_points"] reason_code = message.data["point_gain"][ "reason_code"] logger.info( f"+{earned} → {ws.streamers[streamer_index]} - Reason: {reason_code}.", extra={ "emoji": ":rocket:", "color": Settings.logger.color_palette.get( f"GAIN_FOR_{reason_code}"), }, ) ws.streamers[streamer_index].update_history( reason_code, earned) ws.streamers[ streamer_index].persistent_annotations( reason_code, f"+{earned} - {reason_code}") elif message.type == "claim-available": ws.twitch.claim_bonus( ws.streamers[streamer_index], message.data["claim"]["id"], ) elif message.topic == "video-playback-by-id": # There is stream-up message type, but it's sent earlier than the API updates if message.type == "stream-up": ws.streamers[streamer_index].stream_up = time.time( ) elif message.type == "stream-down": if ws.streamers[streamer_index].is_online is True: ws.streamers[streamer_index].set_offline() elif message.type == "viewcount": if ws.streamers[streamer_index].stream_up_elapsed( ): ws.twitch.check_streamer_online( ws.streamers[streamer_index]) elif message.topic == "raid": if message.type == "raid_update_v2": raid = Raid( message.message["raid"]["id"], message.message["raid"]["target_login"], ) ws.twitch.update_raid(ws.streamers[streamer_index], raid) elif message.topic == "predictions-channel-v1": event_dict = message.data["event"] event_id = event_dict["id"] event_status = event_dict["status"] current_tmsp = parser.parse(message.timestamp) if (message.type == "event-created" and event_id not in ws.events_predictions): if event_status == "ACTIVE": prediction_window_seconds = float( event_dict["prediction_window_seconds"]) # Reduce prediction window by 3/6s - Collect more accurate data for decision prediction_window_seconds -= random.uniform( 3, 6) event = EventPrediction( ws.streamers[streamer_index], event_id, event_dict["title"], parser.parse(event_dict["created_at"]), prediction_window_seconds, event_status, event_dict["outcomes"], ) if (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) 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, }, ) elif (message.type == "event-updated" and event_id in ws.events_predictions): ws.events_predictions[ event_id].status = event_status # Game over we can't update anymore the values... The bet was placed! if (ws.events_predictions[event_id].bet_placed is False and ws.events_predictions[event_id]. bet.decision == {}): ws.events_predictions[ event_id].bet.update_outcomes( event_dict["outcomes"]) elif message.topic == "predictions-user-v1": event_id = message.data["prediction"]["event_id"] if event_id in ws.events_predictions: event_prediction = ws.events_predictions[event_id] if (message.type == "prediction-result" and event_prediction.bet_confirmed): points = event_prediction.parse_result( message.data["prediction"]["result"]) decision = event_prediction.bet.get_decision() choice = event_prediction.bet.decision[ "choice"] logger.info( f"{event_prediction} - Decision: {choice}: {decision['title']} ({decision['color']}) - Result: {event_prediction.result['string']}", extra={ "emoji": ":bar_chart:", "color": Settings.logger.color_palette.get( f"BET_{event_prediction.result['type']}" ), }, ) ws.streamers[streamer_index].update_history( "PREDICTION", points["gained"]) # Remove duplicate history records from previous message sent in community-points-user-v1 if event_prediction.result["type"] == "REFUND": ws.streamers[ streamer_index].update_history( "REFUND", -points["placed"], counter=-1, ) elif event_prediction.result["type"] == "WIN": ws.streamers[ streamer_index].update_history( "PREDICTION", -points["won"], counter=-1, ) if event_prediction.result["type"] != "LOSE": ws.streamers[ streamer_index].persistent_annotations( event_prediction.result["type"], f"{ws.events_predictions[event_id].title}", ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True ws.streamers[ streamer_index].persistent_annotations( "PREDICTION_MADE", f"Decision: {event_prediction.bet.decision['choice']} - {event_prediction.title}", ) except Exception: logger.error( f"Exception raised for topic: {message.topic} and message: {message}", exc_info=True, ) elif response["type"] == "RESPONSE" and len(response.get("error", "")) > 0: raise RuntimeError( f"Error while trying to listen for a topic: {response}") elif response["type"] == "RECONNECT": logger.info(f"#{ws.index} - Reconnection required") WebSocketsPool.handle_reconnection(ws) elif response["type"] == "PONG": ws.last_pong = time.time()
def on_message(ws, message): logger.debug(f"#{ws.index} - Received: {message.strip()}") response = json.loads(message) if response["type"] == "MESSAGE": # We should create a Message class ... message = Message(response["data"]) # If we have more than one PubSub connection, messages may be duplicated # Check the concatenation between message_type.top.channel_id if (ws.last_message_type_channel is not None and ws.last_message_timestamp is not None and ws.last_message_timestamp == message.timestamp and ws.last_message_type_channel == message.identifier): return ws.last_message_timestamp = message.timestamp ws.last_message_type_channel = message.identifier streamer_index = get_streamer_index(ws.streamers, message.channel_id) if streamer_index != -1: try: if message.topic == "community-points-user-v1": if message.type == "points-earned": earned = message.data["point_gain"]["total_points"] reason_code = message.data["point_gain"][ "reason_code"] balance = message.data["balance"]["balance"] ws.streamers[ streamer_index].channel_points = balance logger.info( f"+{earned} → {ws.streamers[streamer_index]} - Reason: {reason_code}.", extra={"emoji": ":rocket:"}, ) ws.streamers[streamer_index].update_history( reason_code, earned) elif message.type == "claim-available": ws.twitch.claim_bonus( ws.streamers[streamer_index], message.data["claim"]["id"], ) elif message.topic == "video-playback-by-id": # There is stream-up message type, but it's sent earlier than the API updates if message.type == "stream-up": ws.streamers[streamer_index].stream_up = time.time( ) elif message.type == "stream-down": if ws.streamers[streamer_index].is_online is True: ws.streamers[streamer_index].set_offline() elif message.type == "viewcount": if ws.streamers[streamer_index].stream_up_elapsed( ): ws.twitch.check_streamer_online( ws.streamers[streamer_index]) elif message.topic == "raid": if message.type == "raid_update_v2": raid = Raid( message.message["raid"]["id"], message.message["raid"]["target_login"], ) ws.twitch.update_raid(ws.streamers[streamer_index], raid) elif message.topic == "predictions-channel-v1": event_dict = message.data["event"] event_id = event_dict["id"] event_status = event_dict["status"] current_tmsp = parser.parse(message.timestamp) if (message.type == "event-created" and event_id not in ws.events_predictions): if event_status == "ACTIVE": prediction_window_seconds = float( event_dict["prediction_window_seconds"]) # Reduce prediction window by 3/6s - Collect more accurate data for decision prediction_window_seconds -= random.uniform( 3, 6) event = EventPrediction( ws.streamers[streamer_index], event_id, event_dict["title"], parser.parse(event_dict["created_at"]), prediction_window_seconds, event_status, event_dict["outcomes"], ) if (ws.streamers[streamer_index].is_online and event.closing_bet_after(current_tmsp) > 0): if event.streamer.viewer_is_mod is True: logger.info( 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( 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:"}, ) elif (message.type == "event-updated" and event_id in ws.events_predictions): ws.events_predictions[ event_id].status = event_status # Game over we can't update anymore the values... The bet was placed! if (ws.events_predictions[event_id].bet_placed is False and ws.events_predictions[event_id]. bet.decision == {}): ws.events_predictions[ event_id].bet.update_outcomes( event_dict["outcomes"]) elif message.topic == "predictions-user-v1": event_id = message.data["prediction"]["event_id"] if event_id in ws.events_predictions: event_prediction = ws.events_predictions[event_id] if (message.type == "prediction-result" and event_prediction.bet_confirmed): event_result = message.data["prediction"][ "result"] result_type = event_result["type"] points_placed = event_prediction.bet.decision[ "amount"] points_won = (event_result["points_won"] if event_result["points_won"] or result_type == "REFUND" else 0) points_gained = (points_won - points_placed if result_type != "REFUND" else 0) points_prefix = "+" if points_gained >= 0 else "" logger.info( f"{ws.events_predictions[event_id]} - Result: {result_type}, Gained: {points_prefix}{_millify(points_gained)}", extra={"emoji": ":bar_chart:"}, ) ws.events_predictions[ event_id].final_result = { "type": event_result["type"], "points_won": points_won, "gained": points_gained, } ws.streamers[streamer_index].update_history( "PREDICTION", points_gained) # Remove duplicate history records from previous message sent in community-points-user-v1 if result_type == "REFUND": ws.streamers[ streamer_index].update_history( "REFUND", -points_placed, counter=-1, ) elif result_type == "WIN": ws.streamers[ streamer_index].update_history( "PREDICTION", -points_won, counter=-1, ) elif message.type == "prediction-made": event_prediction.bet_confirmed = True elif message.topic == "user-drop-events": if message.type == "drop-progress": current = message.data["current_progress_min"] required = message.data["required_progress_min"] if current >= required: try: drop = ws.twitch.search_drop_in_inventory( ws.streamers[streamer_index], message.data["drop_id"], ) if drop["dropInstanceID"] is not None: ws.twitch.claim_drop( drop["dropInstanceID"], ws.streamers[streamer_index], ) except TimeBasedDropNotFound: logger.error( f"Unable to find {message.data['drop_id']} in your inventory" ) else: # Skip 0% and 100% ... percentage_state = int( (current / required) * 100) if percentage_state != 0 and percentage_state % 25 == 0: logger.info( f"Drop event {percentage_state}% for {ws.streamers[streamer_index]}!", extra={"emoji": ":package:"}, ) except Exception: logger.error( f"Exception raised for topic: {message.topic} and message: {message}", exc_info=True, ) elif response["type"] == "RESPONSE" and len(response.get("error", "")) > 0: raise RuntimeError( f"Error while trying to listen for a topic: {response}") elif response["type"] == "RECONNECT": logger.info(f"#{ws.index} - Reconnection required") WebSocketsPool.handle_reconnection(ws) elif response["type"] == "PONG": ws.last_pong = time.time()
def place_bet(self, event: EventPrediction): logger.info( f"Going to complete bet for {event} owned by {event.streamer}", extra={"emoji": ":wrench:"}, ) if event.status == "ACTIVE": if event.box_fillable and self.currently_is_betting: div_bet_is_open = False self.__debug(event, "place_bet") try: WebDriverWait(self.browser, 1).until( expected_conditions.visibility_of_element_located( (By.XPATH, Selectors.betMainDivXP) ) ) div_bet_is_open = True except TimeoutException: logger.info( "The bet div was not found, maybe It was closed. Attempting to open again... Hopefully in time!", extra={"emoji": ":wrench:"}, ) div_bet_is_open = self.__bet_chains_methods(event) if div_bet_is_open is True: logger.info( "Success! Bet div is now open, we can complete the bet!", extra={"emoji": ":wrench:"}, ) if div_bet_is_open is True: decision = event.bet.calculate(event.streamer.channel_points) if decision["choice"] is not None: selector_index = 1 if decision["choice"] == "A" else 2 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(5, 10)) except Exception: logger.error("Exception raised", exc_info=True) else: logger.info( "Sorry, unable to complete the bet. The bet div is still closed!" ) 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