Exemplo n.º 1
0
    async def _errorlogs_scroll(self,
                                ctx: commands.Context,
                                page_size: int = 25,
                                num_pages: int = 15):
        """Scroll through the console's history.

        __**Arguments**__
        `page_size`: (integer) The initial number of lines in each
        page.
        `num_pages`: (integer) The number of pages to read into the
        buffer.
        """
        latest_logs = []
        for path in (data_manager.core_data_path() / "logs").iterdir():
            match = LATEST_LOG_RE.match(path.name)
            if match:
                latest_logs.append(path)

        if not latest_logs:
            await ctx.send("Nothing seems to have been logged yet!")
            return

        latest_logs.sort(reverse=True)

        task = asyncio.create_task(
            LogScrollingMenu.send(ctx, latest_logs, page_size, num_pages))
        task.add_done_callback(self._remove_task)
        self._tasks.append(task)
Exemplo n.º 2
0
async def create_backup(dest: Path = Path.home()) -> Optional[Path]:
    data_path = Path(data_manager.core_data_path().parent)
    if not data_path.exists():
        return None

    dest.mkdir(parents=True, exist_ok=True)
    timestr = datetime.utcnow().strftime("%Y-%m-%dT%H-%M-%S")
    backup_fpath = dest / f"redv3_{data_manager.instance_name}_{timestr}.tar.gz"

    to_backup = []
    exclusions = [
        "__pycache__",
        "Lavalink.jar",
        os.path.join("Downloader", "lib"),
        os.path.join("CogManager", "cogs"),
        os.path.join("RepoManager", "repos"),
        os.path.join("Audio", "logs"),
    ]

    # Avoiding circular imports
    from ...cogs.downloader.repo_manager import RepoManager

    repo_mgr = RepoManager()
    await repo_mgr.initialize()
    repo_output = []
    for repo in repo_mgr.repos:
        repo_output.append({
            "url": repo.url,
            "name": repo.name,
            "branch": repo.branch
        })
    repos_file = data_path / "cogs" / "RepoManager" / "repos.json"
    with repos_file.open("w") as fs:
        json.dump(repo_output, fs, indent=4)
    instance_file = data_path / "instance.json"
    with instance_file.open("w") as fs:
        json.dump({data_manager.instance_name: data_manager.basic_config},
                  fs,
                  indent=4)
    for f in data_path.glob("**/*"):
        if not any(ex in str(f) for ex in exclusions) and f.is_file():
            to_backup.append(f)

    with tarfile.open(str(backup_fpath), "w:gz") as tar:
        for f in to_backup:
            tar.add(str(f),
                    arcname=str(f.relative_to(data_path)),
                    recursive=False)
    return backup_fpath
Exemplo n.º 3
0
async def remove_instance(
    instance: str,
    interactive: bool = False,
    delete_data: Optional[bool] = None,
    _create_backup: Optional[bool] = None,
    drop_db: Optional[bool] = None,
    remove_datapath: Optional[bool] = None,
) -> None:
    data_manager.load_basic_configuration(instance)
    backend = get_current_backend(instance)

    if interactive is True and delete_data is None:
        msg = "Would you like to delete this instance's data?"
        if backend != BackendType.JSON:
            msg += " The database server must be running for this to work."
        delete_data = click.confirm(msg, default=False)

    if interactive is True and _create_backup is None:
        msg = "Would you like to make a backup of the data for this instance?"
        if backend != BackendType.JSON:
            msg += " The database server must be running for this to work."
        _create_backup = click.confirm(msg, default=False)

    if _create_backup is True:
        await create_backup(instance)

    driver_cls = drivers.get_driver_class(backend)
    if delete_data is True:
        await driver_cls.initialize(**data_manager.storage_details())
        try:
            await driver_cls.delete_all_data(interactive=interactive,
                                             drop_db=drop_db)
        finally:
            await driver_cls.teardown()

    if interactive is True and remove_datapath is None:
        remove_datapath = click.confirm(
            "Would you like to delete the instance's entire datapath?",
            default=False)

    if remove_datapath is True:
        data_path = data_manager.core_data_path().parent
        safe_delete(data_path)

    save_config(instance, {}, remove=True)
    print("The instance {} has been removed.".format(instance))
Exemplo n.º 4
0
def init_loggers(cli_flags):
    # d.py stuff
    dpy_logger = logging.getLogger("discord")
    dpy_logger.setLevel(logging.WARNING)
    console = logging.StreamHandler()
    console.setLevel(logging.WARNING)
    dpy_logger.addHandler(console)

    # Red stuff

    logger = logging.getLogger("red")

    red_format = logging.Formatter(
        "%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: %(message)s",
        datefmt="[%d/%m/%Y %H:%M]",
    )

    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setFormatter(red_format)

    if cli_flags.debug:
        os.environ["PYTHONASYNCIODEBUG"] = "1"
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.WARNING)

    from redbot.core.data_manager import core_data_path

    logfile_path = core_data_path() / "red.log"
    fhandler = logging.handlers.RotatingFileHandler(filename=str(logfile_path),
                                                    encoding="utf-8",
                                                    mode="a",
                                                    maxBytes=10**7,
                                                    backupCount=5)
    fhandler.setFormatter(red_format)

    logger.addHandler(fhandler)
    logger.addHandler(stdout_handler)

    # Sentry stuff
    sentry_logger = logging.getLogger("red.sentry")
    sentry_logger.setLevel(logging.WARNING)

    return logger, sentry_logger
Exemplo n.º 5
0
async def remove_instance(
    instance,
    interactive: bool = False,
    delete_data: Optional[bool] = None,
    _create_backup: Optional[bool] = None,
    drop_db: Optional[bool] = None,
    remove_datapath: Optional[bool] = None,
):
    data_manager.load_basic_configuration(instance)

    if interactive is True and delete_data is None:
        delete_data = click.confirm(
            "Would you like to delete this instance's data?", default=False
        )

    if interactive is True and _create_backup is None:
        _create_backup = click.confirm(
            "Would you like to make a backup of the data for this instance?", default=False
        )

    if _create_backup is True:
        await create_backup(instance)

    backend = get_current_backend(instance)
    driver_cls = drivers.get_driver_class(backend)

    if delete_data is True:
        await driver_cls.delete_all_data(interactive=interactive, drop_db=drop_db)

    if interactive is True and remove_datapath is None:
        remove_datapath = click.confirm(
            "Would you like to delete the instance's entire datapath?", default=False
        )

    if remove_datapath is True:
        data_path = data_manager.core_data_path().parent
        safe_delete(data_path)

    save_config(instance, {}, remove=True)
    print("The instance {} has been removed\n".format(instance))
Exemplo n.º 6
0
async def mongo_to_json(instance):
    load_basic_configuration(instance)

    from redbot.core.drivers.red_mongo import Mongo

    m = Mongo("Core", "0", **storage_details())
    db = m.db
    collection_names = await db.list_collection_names()
    for collection_name in collection_names:
        if "." in collection_name:
            # Fix for one of Zeph's problems
            continue
        elif collection_name == "Core":
            c_data_path = core_data_path()
        else:
            c_data_path = cog_data_path(raw_name=collection_name)
        c_data_path.mkdir(parents=True, exist_ok=True)
        # Every cog name has its own collection
        collection = db[collection_name]
        async for document in collection.find():
            # Every cog has its own document.
            # This means if two cogs have the same name but different identifiers, they will
            # be two separate documents in the same collection
            cog_id = document.pop("_id")
            if not isinstance(cog_id, str):
                # Another garbage data check
                continue
            elif not str(cog_id).isdigit():
                continue
            driver = JSON(collection_name,
                          cog_id,
                          data_path_override=c_data_path)
            for category, value in document.items():
                ident_data = IdentifierData(str(cog_id), category, (), (), {})
                await driver.set(ident_data, value=value)
    return {}
Exemplo n.º 7
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)
Exemplo n.º 8
0
async def mongov2_to_json(instance):
    load_basic_configuration(instance)

    core_path = core_data_path()

    from redbot.core.drivers import red_json

    core_conf = Config.get_core_conf()
    new_driver = red_json.JSON(cog_name="Core",
                               identifier="0",
                               data_path_override=core_path)

    core_conf.init_custom("CUSTOM_GROUPS", 2)
    custom_group_data = await core_conf.custom("CUSTOM_GROUPS").all()

    curr_custom_data = custom_group_data.get("Core", {}).get("0", {})
    exported_data = await core_conf.driver.export_data(curr_custom_data)
    conversion_log.info("Starting Core conversion...")
    await new_driver.import_data(exported_data, curr_custom_data)
    conversion_log.info("Core conversion complete.")

    collection_names = await core_conf.driver.db.list_collection_names()
    splitted_names = list(
        filter(
            lambda elem: elem[1] != "" and elem[0] != "Core",
            [n.split(".") for n in collection_names],
        ))

    ident_map = {}  # Cogname: idents list
    for cog_name, category in splitted_names:
        if cog_name not in ident_map:
            ident_map[cog_name] = set()

        idents = await core_conf.driver.db[cog_name][category].distinct(
            "_id.RED_uuid")
        ident_map[cog_name].update(set(idents))

    for cog_name, idents in ident_map.items():
        for identifier in idents:
            curr_custom_data = custom_group_data.get(cog_name,
                                                     {}).get(identifier, {})
            try:
                conf = Config.get_conf(None,
                                       int(identifier),
                                       cog_name=cog_name)
            except ValueError:
                continue
            exported_data = await conf.driver.export_data(curr_custom_data)

            new_path = cog_data_path(raw_name=cog_name)
            new_driver = red_json.JSON(cog_name,
                                       identifier,
                                       data_path_override=new_path)
            conversion_log.info(
                f"Converting {cog_name} with identifier {identifier}...")
            await new_driver.import_data(exported_data, curr_custom_data)

    # cog_data_path(raw_name=cog_name)

    conversion_log.info("Cog conversion complete.")

    return {}
Exemplo n.º 9
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
Exemplo n.º 10
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
Exemplo n.º 11
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
Exemplo n.º 12
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.")