def handle_reconnection(ws):
        # Close the current WebSocket.
        ws.is_closed = True
        ws.keep_running = False
        # Reconnect only if ws.forced_close is False (replace the keep_running)

        # Set the current socket as reconnecting status
        # So the external ping check will be locked
        ws.is_reconneting = True

        if ws.forced_close is False:
            logger.info(
                f"#{ws.index} - Reconnecting to Twitch PubSub server in ~60 seconds"
            )
            time.sleep(30)

            while internet_connection_available() is False:
                random_sleep = random.randint(1, 3)
                logger.warning(
                    f"#{ws.index} - No internet connection available! Retry after {random_sleep}m"
                )
                time.sleep(random_sleep * 60)

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

            for topic in ws.topics:
                self.__submit(ws.index, topic)
 def __check_connection_handler(self, chunk_size):
     # The success rate It's very hight usually. Why we have failed?
     # Check internet connection ...
     while internet_connection_available() is False:
         random_sleep = random.randint(1, 3)
         logger.warning(
             f"No internet connection available! Retry after {random_sleep}m"
         )
         self.__chuncked_sleep(random_sleep * 60, chunk_size=chunk_size)
    def run(self, streamers: list = [], followers=False):
        if self.running:
            logger.error("You can't start multiple sessions of this instance!")
        else:
            logger.info(
                f"Start session: '{self.session_id}'", extra={"emoji": ":bomb:"}
            )
            self.running = True
            self.start_datetime = datetime.now()

            self.twitch.login()

            if self.claim_drops_startup is True:
                self.twitch.claim_all_drops_from_inventory()

            streamers_name: list = []
            streamers_dict: dict = {}

            for streamer in streamers:
                username = (
                    streamer.username
                    if isinstance(streamer, Streamer)
                    else streamer.lower().strip()
                )
                streamers_name.append(username)
                streamers_dict[username] = streamer

            if followers is True:
                followers_array = self.twitch.get_followers()
                logger.info(
                    f"Load {len(followers_array)} followers from your profile!",
                    extra={"emoji": ":clipboard:"},
                )
                for username in followers_array:
                    if username not in streamers_dict:
                        streamers_dict[username] = username.lower().strip()
            else:
                followers_array = []

            streamers_name = list(
                OrderedDict.fromkeys(streamers_name + followers_array)
            )

            logger.info(
                f"Loading data for {len(streamers_name)} streamers. Please wait...",
                extra={"emoji": ":nerd_face:"},
            )
            for username in streamers_name:
                time.sleep(random.uniform(0.3, 0.7))
                try:

                    if isinstance(streamers_dict[username], Streamer) is True:
                        streamer = streamers_dict[username]
                    else:
                        streamer = Streamer(username)

                    streamer.channel_id = self.twitch.get_channel_id(username)
                    streamer.settings = set_default_settings(
                        streamer.settings, Settings.streamer_settings
                    )
                    streamer.settings.bet = set_default_settings(
                        streamer.settings.bet, Settings.streamer_settings.bet
                    )

                    self.streamers.append(streamer)
                except StreamerDoesNotExistException:
                    logger.info(
                        f"Streamer {username} does not exist",
                        extra={"emoji": ":cry:"},
                    )

            # Populate the streamers with default values.
            # 1. Load channel points and auto-claim bonus
            # 2. Check if streamers are online
            # 3. Check if the user is a Streamer. In thi case you can't do prediction
            for streamer in self.streamers:
                time.sleep(random.uniform(0.3, 0.7))
                self.twitch.load_channel_points_context(streamer)
                self.twitch.check_streamer_online(streamer)
                self.twitch.viewer_is_mod(streamer)
                if streamer.viewer_is_mod is True:
                    streamer.settings.make_predictions = False

            self.original_streamers = copy.deepcopy(self.streamers)

            # If we have at least one streamer with settings = make_predictions True
            make_predictions = at_least_one_value_in_settings_is(
                self.streamers, "make_predictions", True
            )

            self.minute_watcher_thread = threading.Thread(
                target=self.twitch.send_minute_watched_events,
                args=(
                    self.streamers,
                    at_least_one_value_in_settings_is(
                        self.streamers, "watch_streak", True
                    ),
                ),
            )
            self.minute_watcher_thread.name = "Minute watcher"
            self.minute_watcher_thread.start()

            self.ws_pool = WebSocketsPool(
                twitch=self.twitch,
                streamers=self.streamers,
                events_predictions=self.events_predictions,
            )

            # Subscribe to community-points-user. Get update for points spent or gains
            self.ws_pool.submit(
                PubsubTopic(
                    "community-points-user-v1",
                    user_id=self.twitch.twitch_login.get_user_id(),
                )
            )

            # If we have at least one streamer with settings = claim_drops True
            # Going to subscribe to user-drop-events. Get update for drop-progress
            claim_drops = at_least_one_value_in_settings_is(
                self.streamers, "claim_drops", True
            )
            if claim_drops is True:
                self.ws_pool.submit(
                    PubsubTopic(
                        "user-drop-events",
                        user_id=self.twitch.twitch_login.get_user_id(),
                    )
                )

            # Going to subscribe to predictions-user-v1. Get update when we place a new prediction (confirm)
            if make_predictions is True:
                self.ws_pool.submit(
                    PubsubTopic(
                        "predictions-user-v1",
                        user_id=self.twitch.twitch_login.get_user_id(),
                    )
                )

            for streamer in self.streamers:
                self.ws_pool.submit(
                    PubsubTopic("video-playback-by-id", streamer=streamer)
                )

                if streamer.settings.follow_raid is True:
                    self.ws_pool.submit(PubsubTopic("raid", streamer=streamer))

                if streamer.settings.make_predictions is True:
                    self.ws_pool.submit(
                        PubsubTopic("predictions-channel-v1", streamer=streamer)
                    )

            while self.running:
                time.sleep(random.uniform(20, 60))
                # Do an external control for WebSocket. Check if the thread is running
                # Check if is not None because maybe we have already created a new connection on array+1 and now index is None
                for index in range(0, len(self.ws_pool.ws)):
                    if (
                        self.ws_pool.ws[index].is_reconneting is False
                        and self.ws_pool.ws[index].elapsed_last_ping() > 10
                        and internet_connection_available() is True
                    ):
                        logger.info(
                            f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..."
                        )
                        WebSocketsPool.handle_reconnection(self.ws_pool.ws[index])
예제 #4
0
    def run(self, streamers: list = [], blacklist: list = [], followers=False):
        if self.running:
            logger.error("You can't start multiple sessions of this instance!")
        else:
            logger.info(f"Start session: '{self.session_id}'",
                        extra={"emoji": ":bomb:"})
            self.running = True
            self.start_datetime = datetime.now()

            self.twitch.login()

            if self.claim_drops_startup is True:
                self.twitch.claim_all_drops_from_inventory()

            streamers_name: list = []
            streamers_dict: dict = {}

            for streamer in streamers:
                username = (streamer.username if isinstance(
                    streamer, Streamer) else streamer.lower().strip())
                if username not in blacklist:
                    streamers_name.append(username)
                    streamers_dict[username] = streamer

            if followers is True:
                followers_array = self.twitch.get_followers()
                logger.info(
                    f"Load {len(followers_array)} followers from your profile!",
                    extra={"emoji": ":clipboard:"},
                )
                for username in followers_array:
                    if username not in streamers_dict and username not in blacklist:
                        streamers_name.append(username)
                        streamers_dict[username] = username.lower().strip()

            logger.info(
                f"Loading data for {len(streamers_name)} streamers. Please wait...",
                extra={"emoji": ":nerd_face:"},
            )
            for username in streamers_name:
                if username in streamers_name:
                    time.sleep(random.uniform(0.3, 0.7))
                    try:
                        streamer = (streamers_dict[username] if isinstance(
                            streamers_dict[username], Streamer) is True else
                                    Streamer(username))
                        streamer.channel_id = self.twitch.get_channel_id(
                            username)
                        streamer.settings = set_default_settings(
                            streamer.settings, Settings.streamer_settings)
                        streamer.settings.bet = set_default_settings(
                            streamer.settings.bet,
                            Settings.streamer_settings.bet)
                        if streamer.settings.join_chat is True:
                            streamer.irc_chat = ThreadChat(
                                self.username,
                                self.twitch.twitch_login.get_auth_token(),
                                streamer.username,
                            )
                        self.streamers.append(streamer)
                    except StreamerDoesNotExistException:
                        logger.info(
                            f"Streamer {username} does not exist",
                            extra={"emoji": ":cry:"},
                        )

            # Populate the streamers with default values.
            # 1. Load channel points and auto-claim bonus
            # 2. Check if streamers are online
            # 3. DEACTIVATED: Check if the user is a moderator. (was used before the 5th of April 2021 to deactivate predictions)
            for streamer in self.streamers:
                time.sleep(random.uniform(0.3, 0.7))
                self.twitch.load_channel_points_context(streamer)
                self.twitch.check_streamer_online(streamer)
                # self.twitch.viewer_is_mod(streamer)

            self.original_streamers = [
                streamer.channel_points for streamer in self.streamers
            ]

            # If we have at least one streamer with settings = make_predictions True
            make_predictions = at_least_one_value_in_settings_is(
                self.streamers, "make_predictions", True)

            # If we have at least one streamer with settings = claim_drops True
            # Spawn a thread for sync inventory and dashboard
            if (at_least_one_value_in_settings_is(
                    self.streamers, "claim_drops", True) is True):
                self.sync_campaigns_thread = threading.Thread(
                    target=self.twitch.sync_campaigns,
                    args=(self.streamers, ),
                )
                self.sync_campaigns_thread.name = "Sync campaigns/inventory"
                self.sync_campaigns_thread.start()
                time.sleep(30)

            self.minute_watcher_thread = threading.Thread(
                target=self.twitch.send_minute_watched_events,
                args=(self.streamers, self.priority),
            )
            self.minute_watcher_thread.name = "Minute watcher"
            self.minute_watcher_thread.start()

            self.ws_pool = WebSocketsPool(
                twitch=self.twitch,
                streamers=self.streamers,
                events_predictions=self.events_predictions,
            )

            # 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=user_id,
                ))

            # Going to subscribe to predictions-user-v1. Get update when we place a new prediction (confirm)
            if make_predictions is True:
                self.ws_pool.submit(
                    PubsubTopic(
                        "predictions-user-v1",
                        user_id=user_id,
                    ))

            for streamer in self.streamers:
                self.ws_pool.submit(
                    PubsubTopic("video-playback-by-id", streamer=streamer))

                if streamer.settings.follow_raid is True:
                    self.ws_pool.submit(PubsubTopic("raid", streamer=streamer))

                if streamer.settings.make_predictions is True:
                    self.ws_pool.submit(
                        PubsubTopic("predictions-channel-v1",
                                    streamer=streamer))

            refresh_context = time.time()
            while self.running:
                time.sleep(random.uniform(20, 60))
                # Do an external control for WebSocket. Check if the thread is running
                # Check if is not None because maybe we have already created a new connection on array+1 and now index is None
                for index in range(0, len(self.ws_pool.ws)):
                    if (self.ws_pool.ws[index].is_reconneting is False
                            and self.ws_pool.ws[index].elapsed_last_ping() > 10
                            and internet_connection_available() is True):
                        logger.info(
                            f"#{index} - The last PING was sent more than 10 minutes ago. Reconnecting to the WebSocket..."
                        )
                        WebSocketsPool.handle_reconnection(
                            self.ws_pool.ws[index])

                if ((time.time() - refresh_context) // 60) >= 30:
                    refresh_context = time.time()
                    for index in range(0, len(self.streamers)):
                        if self.streamers[index].is_online:
                            self.twitch.load_channel_points_context(
                                self.streamers[index])
예제 #5
0
    def send_minute_watched_events(self,
                                   streamers,
                                   watch_streak=False,
                                   chunk_size=3):
        while self.running:
            streamers_index = [
                i for i in range(0, len(streamers))
                if streamers[i].is_online and (streamers[i].online_at == 0 or (
                    time.time() - streamers[i].online_at) > 30)
            ]

            for index in streamers_index:
                if (streamers[index].stream.update_elapsed() / 60) > 10:
                    # Why this user It's currently online but the last updated was more than 10minutes ago?
                    # Please perform a manually update and check if the user it's online
                    self.check_streamer_online(streamers[index])
            """
            Check if we need need to change priority based on watch streak
            Viewers receive points for returning for x consecutive streams.
            Each stream must be at least 10 minutes long and it must have been at least 30 minutes since the last stream ended.

            Watch at least 6m for get the +10
            """
            streamers_watching = []
            if watch_streak is True:
                for index in streamers_index:
                    if (streamers[index].settings.watch_streak is True
                            and streamers[index].stream.watch_streak_missing is
                            True and
                        (streamers[index].offline_at == 0 or
                         ((time.time() - streamers[index].offline_at) // 60) >
                         30) and streamers[index].stream.minute_watched < 7):
                        logger.debug(
                            f"Switch priority: {streamers[index]}, WatchStreak missing is {streamers[index].stream.watch_streak_missing} and minute_watched: {round(streamers[index].stream.minute_watched, 2)}"
                        )
                        streamers_watching.append(index)
                        if len(streamers_watching) == 2:
                            break

            if not streamers_watching:
                streamers_watching = streamers_index
            else:
                while len(streamers_watching) < 2 and len(streamers_index) > 1:
                    another_streamer_index = streamers_index.pop(0)
                    if another_streamer_index not in streamers_watching:
                        try:
                            streamers_watching.append(another_streamer_index)
                        except requests.exceptions.ConnectionError as e:
                            logger.error(
                                f"Error while trying to perform a force for streamer's update: {e}"
                            )
            """
            Twitch has a limit - you can't watch more than 2 channels at one time.
            We take the first two streamers from the list as they have the highest priority (based on order or WatchStreak).
            """
            streamers_watching = streamers_watching[:2]

            for index in streamers_watching:
                next_iteration = time.time() + 60 / len(streamers_watching)

                try:
                    response = requests.post(
                        streamers[index].stream.spade_url,
                        data=streamers[index].stream.encode_payload(),
                        headers={"User-Agent": self.user_agent},
                    )
                    logger.debug(
                        f"Send minute watched request for {streamers[index]} - Status code: {response.status_code}"
                    )
                    if response.status_code == 204:
                        streamers[index].stream.update_minute_watched()
                except requests.exceptions.RequestException as e:
                    logger.error(
                        f"Error while trying to send minute watched: {e}")

                    # The success rate It's very hight usually. Why we have failed?
                    # Check internet connection ...
                    while internet_connection_available() is False:
                        random_sleep = random.randint(1, 3)
                        logger.warning(
                            f"No internet connection available! Retry after {random_sleep}m"
                        )
                        time.sleep(random_sleep * 60)

                # Create chunk of sleep of speed-up the break loop after CTRL+C
                sleep_time = max(next_iteration - time.time(), 0) / chunk_size
                for i in range(0, chunk_size):
                    time.sleep(sleep_time)
                    if self.running is False:
                        break

            if not streamers_watching:
                time.sleep(60)