Ejemplo n.º 1
0
Archivo: bot.py Proyecto: Reliku/hikari
    async def start(
        self,
        *,
        activity: typing.Optional[presences.Activity] = None,
        afk: bool = False,
        check_for_updates: bool = True,
        idle_since: typing.Optional[datetime.datetime] = None,
        ignore_session_start_limit: bool = False,
        large_threshold: int = 250,
        shard_ids: typing.Optional[typing.Set[int]] = None,
        shard_count: typing.Optional[int] = None,
        status: presences.Status = presences.Status.ONLINE,
    ) -> None:
        """Start the bot, wait for all shards to become ready, and then return.

        Other Parameters
        ----------------
        activity : typing.Optional[hikari.presences.Activity]
            The initial activity to display in the bot user presence, or
            `builtins.None` (default) to not show any.
        afk : builtins.bool
            The initial AFK state to display in the bot user presence, or
            `builtins.False` (default) to not show any.
        check_for_updates : builtins.bool
            Defaults to `builtins.True`. If `builtins.True`, will check for
            newer versions of `hikari` on PyPI and notify if available.
        idle_since : typing.Optional[datetime.datetime]
            The `datetime.datetime` the user should be marked as being idle
            since, or `builtins.None` (default) to not show this.
        ignore_session_start_limit : builtins.bool
            Defaults to `builtins.False`. If `builtins.False`, then attempting
            to start more sessions than you are allowed in a 24 hour window
            will throw a `hikari.errors.GatewayError` rather than going ahead
            and hitting the IDENTIFY limit, which may result in your token
            being reset. Setting to `builtins.True` disables this behavior.
        large_threshold : builtins.int
            Threshold for members in a guild before it is treated as being
            "large" and no longer sending member details in the `GUILD CREATE`
            event. Defaults to `250`.
        shard_ids : typing.Optional[typing.Set[builtins.int]]
            The shard IDs to create shards for. If not `builtins.None`, then
            a non-`None` `shard_count` must ALSO be provided. Defaults to
            `builtins.None`, which means the Discord-recommended count is used
            for your application instead.
        shard_count : typing.Optional[builtins.int]
            The number of shards to use in the entire distributed application.
            Defaults to `builtins.None` which results in the count being
            determined dynamically on startup.
        status : hikari.presences.Status
            The initial status to show for the user presence on startup.
            Defaults to `hikari.presences.Status.ONLINE`.
        """
        if shard_ids is not None and shard_count is None:
            raise TypeError(
                "Must pass shard_count if specifying shard_ids manually")

        self._validate_activity(activity)

        # Dispatch the update checker, the sharding requirements checker, and dispatch
        # the starting event together to save a little time on startup.
        start_time = time.monotonic()

        if check_for_updates:
            asyncio.create_task(
                ux.check_for_updates(self._http_settings,
                                     self._proxy_settings),
                name="check for package updates",
            )
        requirements_task = asyncio.create_task(
            self._rest.fetch_gateway_bot(),
            name="fetch gateway sharding settings")
        await self.dispatch(lifetime_events.StartingEvent(app=self))
        requirements = await requirements_task

        if shard_count is None:
            shard_count = requirements.shard_count
        if shard_ids is None:
            shard_ids = set(range(shard_count))

        if requirements.session_start_limit.remaining < len(
                shard_ids) and not ignore_session_start_limit:
            _LOGGER.critical(
                "would have started %s session%s, but you only have %s session%s remaining until %s. Starting more "
                "sessions than you are allowed to start may result in your token being reset. To skip this message, "
                "use bot.run(..., ignore_session_start_limit=True) or bot.start(..., ignore_session_start_limit=True)",
                len(shard_ids),
                "s" if len(shard_ids) != 1 else "",
                requirements.session_start_limit.remaining,
                "s" if requirements.session_start_limit.remaining != 1 else "",
                requirements.session_start_limit.reset_at,
            )
            raise errors.GatewayError(
                "Attempted to start more sessions than were allowed in the given time-window"
            )

        _LOGGER.info(
            "planning to start %s session%s... you can start %s session%s before the next window starts at %s",
            len(shard_ids),
            "s" if len(shard_ids) != 1 else "",
            requirements.session_start_limit.remaining,
            "s" if requirements.session_start_limit.remaining != 1 else "",
            requirements.session_start_limit.reset_at,
        )

        for window_start in range(
                0, shard_count,
                requirements.session_start_limit.max_concurrency):
            window = [
                candidate_shard_id for candidate_shard_id in range(
                    window_start, window_start +
                    requirements.session_start_limit.max_concurrency)
                if candidate_shard_id in shard_ids
            ]

            if not window:
                continue
            if self._shards:
                close_waiter = asyncio.create_task(self._closing_event.wait())
                shard_joiners = [
                    asyncio.ensure_future(s.join())
                    for s in self._shards.values()
                ]

                try:
                    # Attempt to wait for all started shards, for 5 seconds, along with the close
                    # waiter.
                    # If the close flag is set (i.e. user invoked bot.close), or one or more shards
                    # die in this time, we shut down immediately.
                    # If we time out, the joining tasks get discarded and we spin up the next
                    # block of shards, if applicable.
                    _LOGGER.info(
                        "the next startup window is in 5 seconds, please wait..."
                    )
                    await aio.first_completed(
                        aio.all_of(*shard_joiners, timeout=5), close_waiter)

                    if not close_waiter.cancelled():
                        _LOGGER.info(
                            "requested to shut down during startup of shards")
                    else:
                        _LOGGER.critical(
                            "one or more shards shut down unexpectedly during bot startup"
                        )
                    return

                except asyncio.TimeoutError:
                    # If any shards stopped silently, we should close.
                    if any(not s.is_alive for s in self._shards.values()):
                        _LOGGER.info(
                            "one of the shards has been manually shut down (no error), will now shut down"
                        )
                        return
                    # new window starts.

                except Exception as ex:
                    _LOGGER.critical(
                        "an exception occurred in one of the started shards during bot startup: %r",
                        ex)
                    raise

            started_shards = await aio.all_of(
                *(self._start_one_shard(
                    activity=activity,
                    afk=afk,
                    idle_since=idle_since,
                    status=status,
                    large_threshold=large_threshold,
                    shard_id=candidate_shard_id,
                    shard_count=shard_count,
                    url=requirements.url,
                ) for candidate_shard_id in window
                  if candidate_shard_id in shard_ids))

            for started_shard in started_shards:
                self._shards[started_shard.id] = started_shard

        await self.dispatch(lifetime_events.StartedEvent(app=self))

        _LOGGER.info("application started successfully in approx %.2f seconds",
                     time.monotonic() - start_time)
Ejemplo n.º 2
0
 def deserialize_started_event(self) -> lifetime_events.StartedEvent:
     return lifetime_events.StartedEvent(app=self._app)