Esempio n. 1
0
    async def on_ready():
        if bot._uptime is not None:
            return

        bot._uptime = datetime.utcnow()

        guilds = len(bot.guilds)
        users = len(set([m for m in bot.get_all_members()]))

        invite_url = discord.utils.oauth_url(bot._app_info.id)

        prefixes = cli_flags.prefix or (await bot._config.prefix())
        lang = await bot._config.locale()
        red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
        dpy_version = discord.__version__

        table_general_info = Table(show_edge=False,
                                   show_header=False,
                                   box=box.MINIMAL)
        table_general_info.add_row("Prefixes", ", ".join(prefixes))
        table_general_info.add_row("Language", lang)
        table_general_info.add_row("Red version", red_version)
        table_general_info.add_row("Discord.py version", dpy_version)
        table_general_info.add_row("Storage type", data_manager.storage_type())

        table_counts = Table(show_edge=False,
                             show_header=False,
                             box=box.MINIMAL)
        # String conversion is needed as Rich doesn't deal with ints
        table_counts.add_row("Shards", str(bot.shard_count))
        table_counts.add_row("Servers", str(guilds))
        if bot.intents.members:  # Lets avoid 0 Unique Users
            table_counts.add_row("Unique Users", str(users))

        outdated_red_message = ""
        rich_outdated_message = ""
        with contextlib.suppress(aiohttp.ClientError, asyncio.TimeoutError):
            pypi_version, py_version_req = await fetch_latest_red_version_info(
            )
            outdated = pypi_version and pypi_version > red_version_info
            if outdated:
                outdated_red_message = _(
                    "Your Red instance is out of date! {} is the current "
                    "version, however you are using {}!").format(
                        pypi_version, red_version)
                rich_outdated_message = (
                    f"[red]Outdated version![/red]\n"
                    f"[red]!!![/red]Version [cyan]{pypi_version}[/] is available, "
                    f"but you're using [cyan]{red_version}[/][red]!!![/red]")
                current_python = platform.python_version()
                extra_update = _(
                    "\n\nWhile the following command should work in most scenarios as it is "
                    "based on your current OS, environment, and Python version, "
                    "**we highly recommend you to read the update docs at <{docs}> and "
                    "make sure there is nothing else that "
                    "needs to be done during the update.**"
                ).format(
                    docs="https://docs.discord.red/en/stable/update_red.html")
                if expected_version(current_python, py_version_req):
                    installed_extras = []
                    for extra, reqs in red_pkg._dep_map.items():
                        if extra is None or extra in {"dev", "all"}:
                            continue
                        try:
                            pkg_resources.require(req.name for req in reqs)
                        except pkg_resources.DistributionNotFound:
                            pass
                        else:
                            installed_extras.append(extra)

                    if installed_extras:
                        package_extras = f"[{','.join(installed_extras)}]"
                    else:
                        package_extras = ""

                    extra_update += _(
                        "\n\nTo update your bot, first shutdown your "
                        "bot then open a window of {console} (Not as admin) and "
                        "run the following:\n\n").format(
                            console=_("Command Prompt") if platform.system() ==
                            "Windows" else _("Terminal"))
                    extra_update += (
                        '```"{python}" -m pip install -U Red-DiscordBot{package_extras}```'
                        .format(python=sys.executable,
                                package_extras=package_extras))
                    extra_update += _(
                        "\nOnce you've started up your bot again, if you have any 3rd-party cogs"
                        " installed we then highly recommend you update them with this command"
                        " in Discord: `[p]cog update`")

                else:
                    extra_update += _(
                        "\n\nYou have Python `{py_version}` and this update "
                        "requires `{req_py}`; you cannot simply run the update command.\n\n"
                        "You will need to follow the update instructions in our docs above, "
                        "if you still need help updating after following the docs go to our "
                        "#support channel in <https://discord.gg/red>").format(
                            py_version=current_python, req_py=py_version_req)
                outdated_red_message += extra_update

        rich_console = rich.get_console()
        rich_console.print(INTRO, style="red", markup=False, highlight=False)
        if guilds:
            rich_console.print(
                Columns(
                    [
                        Panel(table_general_info, title=str(bot.user.name)),
                        Panel(table_counts)
                    ],
                    equal=True,
                    align="center",
                ))
        else:
            rich_console.print(
                Columns([Panel(table_general_info, title=str(bot.user.name))]))

        rich_console.print("Loaded {} cogs with {} commands".format(
            len(bot.cogs), len(bot.commands)))

        if invite_url:
            rich_console.print(
                f"\nInvite URL: {Text(invite_url, style=f'link {invite_url}')}"
            )
            # We generally shouldn't care if the client supports it or not as Rich deals with it.
        if not guilds:
            rich_console.print(
                f"Looking for a quick guide on setting up Red? Checkout {Text('https://start.discord.red', style='link https://start.discord.red}')}"
            )
        if rich_outdated_message:
            rich_console.print(rich_outdated_message)

        bot._color = discord.Colour(await bot._config.color())
        bot._red_ready.set()
        if outdated_red_message:
            await send_to_owners_with_prefix_replaced(bot,
                                                      outdated_red_message)
Esempio n. 2
0
def main():
    description = "Red V3"
    cli_flags = parse_cli_flags(sys.argv[1:])
    if cli_flags.list_instances:
        list_instances()
    elif cli_flags.version:
        print(description)
        print("Current Version: {}".format(__version__))
        sys.exit(0)
    elif not cli_flags.instance_name and not cli_flags.no_instance:
        print("Error: No instance name was provided!")
        sys.exit(1)
    if cli_flags.no_instance:
        print(
            "\033[1m"
            "Warning: The data will be placed in a temporary folder and removed on next system "
            "reboot."
            "\033[0m")
        cli_flags.instance_name = "temporary_red"
        data_manager.create_temp_config()
    data_manager.load_basic_configuration(cli_flags.instance_name)
    redbot.logging.init_logging(level=cli_flags.logging_level,
                                location=data_manager.core_data_path() /
                                "logs")

    log.debug("====Basic Config====")
    log.debug("Data Path: %s", data_manager._base_data_path())
    log.debug("Storage Type: %s", data_manager.storage_type())

    red = Red(cli_flags=cli_flags, description=description, pm_help=None)
    init_global_checks(red)
    init_events(red, cli_flags)
    red.add_cog(Core(red))
    red.add_cog(CogManagerUI())
    if cli_flags.dev:
        red.add_cog(Dev())
    # noinspection PyProtectedMember
    modlog._init()
    # noinspection PyProtectedMember
    bank._init()
    loop = asyncio.get_event_loop()
    if os.name == "posix":
        loop.add_signal_handler(
            SIGTERM, lambda: asyncio.ensure_future(sigterm_handler(red, log)))
    tmp_data = {}
    loop.run_until_complete(_get_prefix_and_token(red, tmp_data))
    token = os.environ.get("RED_TOKEN", tmp_data["token"])
    if cli_flags.token:
        token = cli_flags.token
    prefix = cli_flags.prefix or tmp_data["prefix"]
    if not (token and prefix):
        if cli_flags.no_prompt is False:
            new_token = interactive_config(red,
                                           token_set=bool(token),
                                           prefix_set=bool(prefix))
            if new_token:
                token = new_token
        else:
            log.critical("Token and prefix must be set in order to login.")
            sys.exit(1)
    loop.run_until_complete(_get_prefix_and_token(red, tmp_data))

    if cli_flags.dry_run:
        loop.run_until_complete(red.http.close())
        sys.exit(0)
    try:
        loop.run_until_complete(red.start(token, bot=True))
    except discord.LoginFailure:
        log.critical("This token doesn't seem to be valid.")
        db_token = loop.run_until_complete(red.db.token())
        if db_token and not cli_flags.no_prompt:
            print("\nDo you want to reset the token? (y/n)")
            if confirm("> "):
                loop.run_until_complete(red.db.token.set(""))
                print("Token has been reset.")
    except KeyboardInterrupt:
        log.info("Keyboard interrupt detected. Quitting...")
        loop.run_until_complete(red.logout())
        red._shutdown_mode = ExitCodes.SHUTDOWN
    except Exception as e:
        log.critical("Fatal exception", exc_info=e)
        loop.run_until_complete(red.logout())
    finally:
        pending = asyncio.Task.all_tasks(loop=red.loop)
        gathered = asyncio.gather(*pending,
                                  loop=red.loop,
                                  return_exceptions=True)
        gathered.cancel()
        try:
            loop.run_until_complete(red.rpc.close())
        except AttributeError:
            pass

        sys.exit(red._shutdown_mode.value)
Esempio n. 3
0
async def run_bot(red: Red, cli_flags: Namespace) -> None:
    """
    This runs the bot.

    Any shutdown which is a result of not being able to log in needs to raise
    a SystemExit exception.

    If the bot starts normally, the bot should be left to handle the exit case.
    It will raise SystemExit in a task, which will reach the event loop and
    interrupt running forever, then trigger our cleanup process, and does not
    need additional handling in this function.
    """

    driver_cls = drivers.get_driver_class()

    await driver_cls.initialize(**data_manager.storage_details())

    redbot.logging.init_logging(level=cli_flags.logging_level,
                                location=data_manager.core_data_path() /
                                "logs")

    log.debug("====Basic Config====")
    log.debug("Data Path: %s", data_manager._base_data_path())
    log.debug("Storage Type: %s", data_manager.storage_type())

    # lib folder has to be in sys.path before trying to load any 3rd-party cog (GH-3061)
    # We might want to change handling of requirements in Downloader at later date
    LIB_PATH = data_manager.cog_data_path(raw_name="Downloader") / "lib"
    LIB_PATH.mkdir(parents=True, exist_ok=True)
    if str(LIB_PATH) not in sys.path:
        sys.path.append(str(LIB_PATH))

        # "It's important to note that the global `working_set` object is initialized from
        # `sys.path` when `pkg_resources` is first imported, but is only updated if you do
        # all future `sys.path` manipulation via `pkg_resources` APIs. If you manually modify
        # `sys.path`, you must invoke the appropriate methods on the `working_set` instance
        # to keep it in sync."
        # Source: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#workingset-objects
        pkg_resources.working_set.add_entry(str(LIB_PATH))
    sys.meta_path.insert(0, SharedLibImportWarner())

    if cli_flags.token:
        token = cli_flags.token
    else:
        token = os.environ.get("RED_TOKEN", None)
        if not token:
            token = await red._config.token()

    prefix = cli_flags.prefix or await red._config.prefix()

    if not (token and prefix):
        if cli_flags.no_prompt is False:
            new_token = await interactive_config(red,
                                                 token_set=bool(token),
                                                 prefix_set=bool(prefix))
            if new_token:
                token = new_token
        else:
            log.critical("Token and prefix must be set in order to login.")
            sys.exit(1)

    if cli_flags.dry_run:
        await red.http.close()
        sys.exit(0)
    try:
        await red.start(token, bot=True, cli_flags=cli_flags)
    except discord.LoginFailure:
        log.critical("This token doesn't seem to be valid.")
        db_token = await red._config.token()
        if db_token and not cli_flags.no_prompt:
            if confirm("\nDo you want to reset the token?"):
                await red._config.token.set("")
                print("Token has been reset.")
                sys.exit(0)
        sys.exit(1)
    except discord.PrivilegedIntentsRequired:
        print(
            "Red requires all Privileged Intents to be enabled.\n"
            "You can find out how to enable Privileged Intents with this guide:\n"
            "https://docs.discord.red/en/stable/bot_application_guide.html#enabling-privileged-intents"
        )
        sys.exit(1)

    return None
Esempio n. 4
0
async def run_bot(red: Red, cli_flags: Namespace) -> None:
    """
    This runs the bot.
    
    Any shutdown which is a result of not being able to log in needs to raise
    a SystemExit exception.

    If the bot starts normally, the bot should be left to handle the exit case.
    It will raise SystemExit in a task, which will reach the event loop and
    interrupt running forever, then trigger our cleanup process, and does not
    need additional handling in this function.
    """

    driver_cls = drivers.get_driver_class()

    await driver_cls.initialize(**data_manager.storage_details())

    redbot.logging.init_logging(
        level=cli_flags.logging_level, location=data_manager.core_data_path() / "logs"
    )

    log.debug("====Basic Config====")
    log.debug("Data Path: %s", data_manager._base_data_path())
    log.debug("Storage Type: %s", data_manager.storage_type())

    # lib folder has to be in sys.path before trying to load any 3rd-party cog (GH-3061)
    # We might want to change handling of requirements in Downloader at later date
    LIB_PATH = data_manager.cog_data_path(raw_name="Downloader") / "lib"
    LIB_PATH.mkdir(parents=True, exist_ok=True)
    if str(LIB_PATH) not in sys.path:
        sys.path.append(str(LIB_PATH))
    sys.meta_path.insert(0, SharedLibImportWarner())

    if cli_flags.token:
        token = cli_flags.token
    else:
        token = os.environ.get("RED_TOKEN", None)
        if not token:
            token = await red._config.token()

    prefix = cli_flags.prefix or await red._config.prefix()

    if not (token and prefix):
        if cli_flags.no_prompt is False:
            new_token = await interactive_config(
                red, token_set=bool(token), prefix_set=bool(prefix)
            )
            if new_token:
                token = new_token
        else:
            log.critical("Token and prefix must be set in order to login.")
            sys.exit(1)

    if cli_flags.dry_run:
        await red.http.close()
        sys.exit(0)
    try:
        await red.start(token, bot=True, cli_flags=cli_flags)
    except discord.LoginFailure:
        log.critical("This token doesn't seem to be valid.")
        db_token = await red._config.token()
        if db_token and not cli_flags.no_prompt:
            if confirm("\nDo you want to reset the token?"):
                await red._config.token.set("")
                print("Token has been reset.")
                sys.exit(0)
        sys.exit(1)

    return None
Esempio n. 5
0
    async def on_ready():
        if bot._uptime is not None:
            return

        bot._uptime = datetime.utcnow()

        guilds = len(bot.guilds)
        users = len(set([m for m in bot.get_all_members()]))

        app_info = await bot.application_info()

        if app_info.team:
            if bot._use_team_features:
                bot.owner_ids.update(m.id for m in app_info.team.members)
        elif bot._owner_id_overwrite is None:
            bot.owner_ids.add(app_info.owner.id)
        bot._app_owners_fetched = True

        try:
            invite_url = discord.utils.oauth_url(app_info.id)
        except:
            invite_url = "Could not fetch invite url"

        prefixes = cli_flags.prefix or (await bot._config.prefix())
        lang = await bot._config.locale()
        red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
        dpy_version = discord.__version__

        INFO = [
            str(bot.user),
            "Prefixes: {}".format(", ".join(prefixes)),
            "Language: {}".format(lang),
            "Red Bot Version: {}".format(red_version),
            "Discord.py Version: {}".format(dpy_version),
            "Shards: {}".format(bot.shard_count),
            "Storage type: {}".format(data_manager.storage_type()),
        ]

        if guilds:
            INFO.extend(("Servers: {}".format(guilds), "Users: {}".format(users)))
        else:
            print("Ready. I'm not in any server yet!")

        INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands)))

        outdated_red_message = ""
        with contextlib.suppress(aiohttp.ClientError, asyncio.TimeoutError):
            pypi_version, py_version_req = await fetch_latest_red_version_info()
            outdated = pypi_version and pypi_version > red_version_info
            if outdated:
                INFO.append(
                    "Outdated version! {} is available "
                    "but you're using {}".format(pypi_version, red_version)
                )
                outdated_red_message = _(
                    "Your Red instance is out of date! {} is the current "
                    "version, however you are using {}!"
                ).format(pypi_version, red_version)
                current_python = platform.python_version()
                extra_update = _(
                    "\n\nWhile the following command should work in most scenarios as it is "
                    "based on your current OS, environment, and Python version, "
                    "**we highly recommend you to read the update docs at <{docs}> and "
                    "make sure there is nothing else that "
                    "needs to be done during the update.**"
                ).format(docs="https://docs.discord.red/en/stable/update_red.html")
                if expected_version(current_python, py_version_req):
                    installed_extras = []
                    for extra, reqs in red_pkg._dep_map.items():
                        if extra is None or extra in {"dev", "all"}:
                            continue
                        try:
                            pkg_resources.require(req.name for req in reqs)
                        except pkg_resources.DistributionNotFound:
                            pass
                        else:
                            installed_extras.append(extra)

                    if installed_extras:
                        package_extras = f"[{','.join(installed_extras)}]"
                    else:
                        package_extras = ""

                    extra_update += _(
                        "\n\nTo update your bot, first shutdown your "
                        "bot then open a window of {console} (Not as admin) and "
                        "run the following:\n\n"
                    ).format(
                        console=_("Command Prompt")
                        if platform.system() == "Windows"
                        else _("Terminal")
                    )
                    extra_update += (
                        '```"{python}" -m pip install -U Red-DiscordBot{package_extras}```'.format(
                            python=sys.executable, package_extras=package_extras
                        )
                    )

                else:
                    extra_update += _(
                        "\n\nYou have Python `{py_version}` and this update "
                        "requires `{req_py}`; you cannot simply run the update command.\n\n"
                        "You will need to follow the update instructions in our docs above, "
                        "if you still need help updating after following the docs go to our "
                        "#support channel in <https://discord.gg/red>"
                    ).format(py_version=current_python, req_py=py_version_req)
                outdated_red_message += extra_update

        INFO2 = []

        reqs_installed = {"docs": None, "test": None}
        for key in reqs_installed.keys():
            reqs = [x.name for x in red_pkg._dep_map[key]]
            try:
                pkg_resources.require(reqs)
            except DistributionNotFound:
                reqs_installed[key] = False
            else:
                reqs_installed[key] = True

        options = (
            ("Voice", True),
            ("Docs", reqs_installed["docs"]),
            ("Tests", reqs_installed["test"]),
        )

        on_symbol, off_symbol, ascii_border = _get_startup_screen_specs()

        for option, enabled in options:
            enabled = on_symbol if enabled else off_symbol
            INFO2.append("{} {}".format(enabled, option))

        print(Fore.RED + INTRO)
        print(Style.RESET_ALL)
        print(bordered(INFO, INFO2, ascii_border=ascii_border))

        if invite_url:
            print("\nInvite URL: {}\n".format(invite_url))

        if not guilds:
            print(
                "Looking for a quick guide on setting up Red? https://docs.discord.red/en/stable/getting_started.html\n"
            )

        if not bot.owner_ids:
            # we could possibly exit here in future
            log.warning("Bot doesn't have any owner set!")

        bot._color = discord.Colour(await bot._config.color())
        bot._red_ready.set()
        if outdated_red_message:
            await bot.send_to_owners(outdated_red_message)
Esempio n. 6
0
async def run_bot(red: Red, cli_flags: Namespace):

    driver_cls = drivers.get_driver_class()

    await driver_cls.initialize(**data_manager.storage_details())

    redbot.logging.init_logging(
        level=cli_flags.logging_level, location=data_manager.core_data_path() / "logs"
    )

    log.debug("====Basic Config====")
    log.debug("Data Path: %s", data_manager._base_data_path())
    log.debug("Storage Type: %s", data_manager.storage_type())

    if cli_flags.edit:
        try:
            await edit_instance(red, cli_flags)
        except (KeyboardInterrupt, EOFError):
            print("Aborted!")
        finally:
            await driver_cls.teardown()
        sys.exit(0)

    # lib folder has to be in sys.path before trying to load any 3rd-party cog (GH-3061)
    # We might want to change handling of requirements in Downloader at later date
    LIB_PATH = data_manager.cog_data_path(raw_name="Downloader") / "lib"
    LIB_PATH.mkdir(parents=True, exist_ok=True)
    if str(LIB_PATH) not in sys.path:
        sys.path.append(str(LIB_PATH))
    sys.meta_path.insert(0, SharedLibImportWarner())

    if cli_flags.token:
        token = cli_flags.token
    else:
        token = os.environ.get("RED_TOKEN", None)
        if not token:
            token = await red._config.token()

    prefix = cli_flags.prefix or await red._config.prefix()

    if not (token and prefix):
        if cli_flags.no_prompt is False:
            new_token = await interactive_config(
                red, token_set=bool(token), prefix_set=bool(prefix)
            )
            if new_token:
                token = new_token
        else:
            log.critical("Token and prefix must be set in order to login.")
            sys.exit(1)

    if cli_flags.dry_run:
        await red.http.close()
        sys.exit(0)
    try:
        await red.start(token, bot=True, cli_flags=cli_flags)
    except discord.LoginFailure:
        log.critical("This token doesn't seem to be valid.")
        db_token = await red._config.token()
        if db_token and not cli_flags.no_prompt:
            if confirm("\nDo you want to reset the token?"):
                await red._config.token.set("")
                print("Token has been reset.")