Example #1
0
def _edit_instance_name(old_name, new_name, confirm_overwrite, no_prompt):
    if new_name:
        name = new_name
        if name in _get_instance_names() and not confirm_overwrite:
            name = old_name
            print(
                "An instance with this name already exists.\n"
                "If you want to remove the existing instance and replace it with this one,"
                " run this command with --overwrite-existing-instance flag.")
    elif not no_prompt and confirm(
            "Would you like to change the instance name?", default=False):
        name = get_name()
        if name in _get_instance_names():
            print("WARNING: An instance already exists with this name. "
                  "Continuing will overwrite the existing instance config.")
            if not confirm(
                    "Are you absolutely certain you want to continue with this instance name?",
                    default=False,
            ):
                print("Instance name will remain unchanged.")
                name = old_name
            else:
                print("Instance name updated.")
        else:
            print("Instance name updated.")
        print()
    else:
        name = old_name
    return name
Example #2
0
async def _edit_owner(red, owner, no_prompt):
    if owner:
        if not (15 <= len(str(owner)) <= 21):
            print(
                "The provided owner id doesn't look like a valid Discord user id."
                " Instance's owner will remain unchanged.")
            return
        await red._config.owner.set(owner)
    elif not no_prompt and confirm(
            "Would you like to change instance's owner?", default=False):
        print(
            "Remember:\n"
            "ONLY the person who is hosting Red should be owner."
            " This has SERIOUS security implications."
            " The owner can access any data that is present on the host system.\n"
        )
        if confirm("Are you sure you want to change instance's owner?",
                   default=False):
            print("Please enter a Discord user id for new owner:")
            while True:
                owner_id = input("> ").strip()
                if not (15 <= len(owner_id) <= 21 and owner_id.isdecimal()):
                    print("That doesn't look like a valid Discord user id.")
                    continue
                owner_id = int(owner_id)
                await red._config.owner.set(owner_id)
                print("Owner updated.")
                break
        else:
            print("Instance's owner will remain unchanged.")
        print()
Example #3
0
def get_data_dir():
    default_data_dir = Path(appdir.user_data_dir)

    print("Hello! Before we begin the full configuration process we need to"
          " gather some initial information about where you'd like us"
          " to store your bot's data. We've attempted to figure out a"
          " sane default data location which is printed below. If you don't"
          " want to change this default please press [ENTER], otherwise"
          " input your desired data location.")
    print()
    print("Default: {}".format(default_data_dir))

    new_path = input("> ")

    if new_path != "":
        new_path = Path(new_path)
        default_data_dir = new_path

    if not default_data_dir.exists():
        try:
            default_data_dir.mkdir(parents=True, exist_ok=True)
        except OSError:
            print("We were unable to create your chosen directory."
                  " You may need to restart this process with admin"
                  " privileges.")
            sys.exit(1)

    print("You have chosen {} to be your data directory.".format(
        default_data_dir))
    if not confirm("Please confirm (y/n):"):
        print("Please start the process over.")
        sys.exit(0)
    return default_data_dir
Example #4
0
def get_name(name: str) -> str:
    INSTANCE_NAME_RE = re.compile(r"[A-Za-z0-9_\.\-]*")
    if name:
        if INSTANCE_NAME_RE.fullmatch(name) is None:
            print(
                "ERROR: Instance names can only include characters A-z, numbers, "
                "underscores (_) and periods (.).")
            sys.exit(1)
        return name

    while len(name) == 0:
        print("Please enter a name for your instance,"
              " it will be used to run your bot from here on out.\n"
              "This name is case-sensitive and should only include characters"
              " A-z, numbers, underscores (_) and periods (.).")
        name = input("> ")
        if INSTANCE_NAME_RE.fullmatch(name) is None:
            print(
                "ERROR: Instance names can only include characters A-z, numbers, "
                "underscores (_) and periods (.).")
            name = ""
        elif "-" in name and not confirm(
                "Hyphens (-) in instance names may cause issues. Are you sure you want to continue with this instance name?",
                default=False,
        ):
            name = ""

        print()  # new line for aesthetics
    return name
Example #5
0
async def reset_red():
    instances = load_existing_config()

    if not instances:
        print("No instance to delete.\n")
        return
    print(
        "WARNING: You are about to remove ALL Bot instances on this computer.")
    print("If you want to reset data of only one instance, "
          "please select option 5 in the launcher.")
    await asyncio.sleep(2)
    print("\nIf you continue you will remove these instanes.\n")
    for instance in list(instances.keys()):
        print("    - {}".format(instance))
    await asyncio.sleep(3)
    print('\nIf you want to reset all instances, type "I agree".')
    response = input("> ").strip()
    if response != "I agree":
        print("Cancelling...")
        return

    if confirm("\nDo you want to create a backup for an instance? (y/n) "):
        for index, instance in instances.items():
            print("\nRemoving {}...".format(index))
            await create_backup(index, instance)
            await remove_instance(index, instance)
    else:
        for index, instance in instances.items():
            await remove_instance(index, instance)
    print("All instances have been removed.")
Example #6
0
async def edit_instance():
    instance_list = load_existing_config()
    if not instance_list:
        print("No instances have been set up!")
        return

    print("You have chosen to edit an instance. The following "
          "is a list of instances that currently exist:\n")
    for instance in instance_list.keys():
        print("{}\n".format(instance))
    print("Please select one of the above by entering its name")
    selected = input("> ")

    if selected not in instance_list.keys():
        print("That isn't a valid instance!")
        return
    instance_data = instance_list[selected]
    default_dirs = deepcopy(basic_config_default)

    current_data_dir = Path(instance_data["DATA_PATH"])
    print("You have selected '{}' as the instance to modify.".format(selected))
    if not confirm("Please confirm (y/n):"):
        print("Ok, we will not continue then.")
        return

    print("Ok, we will continue on.")
    print()
    if confirm("Would you like to change the instance name? (y/n)"):
        name = get_name()
    else:
        name = selected

    if confirm("Would you like to change the data location? (y/n)"):
        default_data_dir = get_data_dir()
        default_dirs["DATA_PATH"] = str(default_data_dir.resolve())
    else:
        default_dirs["DATA_PATH"] = str(current_data_dir.resolve())

    if name != selected:
        save_config(selected, {}, remove=True)
    save_config(name, default_dirs)

    print("Your basic configuration has been edited")
Example #7
0
async def _edit_token(red, token, no_prompt):
    if token:
        if not len(token) >= 50:
            print("The provided token doesn't look a valid Discord bot token."
                  " Instance's token will remain unchanged.\n")
            return
        await red._config.token.set(token)
    elif not no_prompt and confirm(
            "Would you like to change instance's token?", default=False):
        await interactive_config(red, False, True, print_header=False)
        print("Token updated.\n")
Example #8
0
async def create_backup(instance):
    instance_vals = instance_data[instance]
    if confirm(
            "Would you like to make a backup of the data for this instance? (y/n)"
    ):
        load_basic_configuration(instance)
        if instance_vals["STORAGE_TYPE"] == "MongoDB":
            await mongo_to_json(instance)
        print("Backing up the instance's data...")
        backup_filename = "redv3-{}-{}.tar.gz".format(
            instance,
            dt.utcnow().strftime("%Y-%m-%d %H-%M-%S"))
        pth = Path(instance_vals["DATA_PATH"])
        if pth.exists():
            backup_pth = pth.home()
            backup_file = backup_pth / backup_filename

            to_backup = []
            exclusions = [
                "__pycache__",
                "Lavalink.jar",
                os.path.join("Downloader", "lib"),
                os.path.join("CogManager", "cogs"),
                os.path.join("RepoManager", "repos"),
            ]
            from redbot.cogs.downloader.repo_manager import RepoManager

            repo_mgr = RepoManager()
            await repo_mgr.initialize()
            repo_output = []
            for repo in repo_mgr._repos.values():
                repo_output.append({
                    "url": repo.url,
                    "name": repo.name,
                    "branch": repo.branch
                })
            repo_filename = pth / "cogs" / "RepoManager" / "repos.json"
            with open(str(repo_filename), "w") as f:
                f.write(json.dumps(repo_output, indent=4))
            instance_vals = {instance_name: basic_config}
            instance_file = pth / "instance.json"
            with open(str(instance_file), "w") as instance_out:
                instance_out.write(json.dumps(instance_vals, indent=4))
            for f in pth.glob("**/*"):
                if not any(ex in str(f) for ex in exclusions):
                    to_backup.append(f)
            with tarfile.open(str(backup_file), "w:gz") as tar:
                for f in to_backup:
                    tar.add(str(f), recursive=False)
            print("A backup of {} has been made. It is at {}".format(
                instance, backup_file))
Example #9
0
def save_config(name, data, remove=False):
    config = load_existing_config()
    if remove and name in config:
        config.pop(name)
    else:
        if name in config:
            print("WARNING: An instance already exists with this name. "
                  "Continuing will overwrite the existing instance config.")
            if not confirm(
                    "Are you absolutely certain you want to continue (y/n)? "):
                print("Not continuing")
                sys.exit(0)
        config[name] = data
    JsonIO(config_file)._save_json(config)
Example #10
0
def _edit_data_path(data, instance_name, data_path, copy_data, no_prompt):
    # This modifies the passed dict.
    if data_path:
        new_path = Path(data_path)
        try:
            exists = new_path.exists()
        except OSError:
            print("We were unable to check your chosen directory."
                  " Provided path may contain an invalid character."
                  " Data location will remain unchanged.")

        if not exists:
            try:
                new_path.mkdir(parents=True, exist_ok=True)
            except OSError:
                print("We were unable to create your chosen directory."
                      " Data location will remain unchanged.")
        data["DATA_PATH"] = data_path
        if copy_data and not _copy_data(data):
            print(
                "Can't copy data to non-empty location. Data location will remain unchanged."
            )
            data["DATA_PATH"] = data_manager.basic_config["DATA_PATH"]
    elif not no_prompt and confirm(
            "Would you like to change the data location?", default=False):
        data["DATA_PATH"] = get_data_dir(instance_name)
        if confirm("Do you want to copy the data from old location?",
                   default=True):
            if not _copy_data(data):
                print("Can't copy the data to non-empty location.")
                if not confirm(
                        "Do you still want to use the new data location?"):
                    data["DATA_PATH"] = data_manager.basic_config["DATA_PATH"]
                    print("Data location will remain unchanged.")
                    return
            print("Old data has been copied over to the new location.")
        print("Data location updated.")
Example #11
0
def save_config(name, data, remove=False):
    config = load_existing_config()
    if remove and name in config:
        config.pop(name)
    else:
        if name in config:
            print("WARNING: An instance already exists with this name. "
                  "Continuing will overwrite the existing instance config.")
            if not confirm(
                    "Are you absolutely certain you want to continue (y/n)? "):
                print("Not continuing")
                sys.exit(0)
        config[name] = data

    with config_file.open("w", encoding="utf-8") as fs:
        json.dump(config, fs, indent=4)
Example #12
0
async def _edit_prefix(red, prefix, no_prompt):
    if prefix:
        prefixes = sorted(prefix, reverse=True)
        await red._config.prefix.set(prefixes)
    elif not no_prompt and confirm("Would you like to change instance's prefixes?", default=False):
        print(
            "Enter the prefixes, separated by a space (please note "
            "that prefixes containing a space will need to be added with [p]set prefix)"
        )
        while True:
            prefixes = input("> ").strip().split()
            if not prefixes:
                print("You need to pass at least one prefix!")
                continue
            prefixes = sorted(prefixes, reverse=True)
            await red._config.prefix.set(prefixes)
            print("Prefixes updated.\n")
            break
Example #13
0
async def run_red_bot(log, 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())

    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 Exception:
        log.exception("hmm")
        raise
    return None
Example #14
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",
        cli_flags=cli_flags,
    )

    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)
    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:
        console = rich.get_console()
        console.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",
            style="red",
        )
        sys.exit(1)
    except _NoOwnerSet:
        print(
            "Bot doesn't have any owner set!\n"
            "This can happen when your bot's application is owned by team"
            " as team members are NOT owners by default.\n\n"
            "Remember:\n"
            "ONLY the person who is hosting Red should be owner."
            " This has SERIOUS security implications."
            " The owner can access any data that is present on the host system.\n"
            "With that out of the way, depending on who you want to be considered as owner,"
            " you can:\n"
            "a) pass --team-members-are-owners when launching Red"
            " - in this case Red will treat all members of the bot application's team as owners\n"
            f"b) set owner manually with `redbot --edit {cli_flags.instance_name}`\n"
            "c) pass owner ID(s) when launching Red with --owner"
            " (and --co-owner if you need more than one) flag\n")
        sys.exit(1)

    return None
Example #15
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
Example #16
0
async def edit_instance():
    instance_list = load_existing_config()
    if not instance_list:
        print("No instances have been set up!")
        return

    print("You have chosen to edit an instance. The following "
          "is a list of instances that currently exist:\n")
    for instance in instance_list.keys():
        print("{}\n".format(instance))
    print("Please select one of the above by entering its name")
    selected = input("> ")

    if selected not in instance_list.keys():
        print("That isn't a valid instance!")
        return
    instance_data = instance_list[selected]
    default_dirs = deepcopy(basic_config_default)

    current_data_dir = Path(instance_data["DATA_PATH"])
    print("You have selected '{}' as the instance to modify.".format(selected))
    if not confirm("Please confirm (y/n):"):
        print("Ok, we will not continue then.")
        return

    print("Ok, we will continue on.")
    print()
    if confirm("Would you like to change the instance name? (y/n)"):
        name = get_name()
    else:
        name = selected

    if confirm("Would you like to change the data location? (y/n)"):
        default_data_dir = get_data_dir()
        default_dirs["DATA_PATH"] = str(default_data_dir.resolve())
    else:
        default_dirs["DATA_PATH"] = str(current_data_dir.resolve())

    if confirm("Would you like to change the storage type? (y/n):"):
        storage = get_storage_type()

        storage_dict = {1: "JSON", 2: "MongoDB"}
        default_dirs["STORAGE_TYPE"] = storage_dict[storage]
        if storage_dict.get(storage, 1) == "MongoDB":
            from redbot.core.drivers.red_mongo import get_config_details

            storage_details = get_config_details()
            default_dirs["STORAGE_DETAILS"] = storage_details

            if instance_data["STORAGE_TYPE"] == "JSON":
                if confirm("Would you like to import your data? (y/n) "):
                    await json_to_mongo(current_data_dir, storage_details)
        else:
            storage_details = instance_data["STORAGE_DETAILS"]
            default_dirs["STORAGE_DETAILS"] = {}
            if instance_data["STORAGE_TYPE"] == "MongoDB":
                if confirm("Would you like to import your data? (y/n) "):
                    await mongo_to_json(current_data_dir, storage_details)

    if name != selected:
        save_config(selected, {}, remove=True)
    save_config(name, default_dirs)

    print("Your basic configuration has been edited")
Example #17
0
def main():
    description = "Bot Base - Version {}".format(__version__)
    cli_flags = parse_cli_flags(sys.argv[1:])
    if cli_flags.list_instances:
        list_instances()
    elif cli_flags.version:
        print(description)
        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"
        create_temp_config()
    load_basic_configuration(cli_flags.instance_name)
    log = init_loggers(cli_flags)
    loop = asyncio.get_event_loop()
    red = Red(cli_flags=cli_flags, description=description, pm_help=None)
    init_global_checks(red)
    init_events(red, cli_flags)
    loop.run_until_complete(red.cog_mgr.initialize())
    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()
    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)
Example #18
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.")