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 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()