def handle_edit(cli_flags: Namespace): """ This one exists to not log all the things like it's a full run of the bot. """ loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) data_manager.load_basic_configuration(cli_flags.instance_name) red = Red(cli_flags=cli_flags, description="Red V3", dm_help=None, fetch_offline_members=True) try: driver_cls = drivers.get_driver_class() loop.run_until_complete( driver_cls.initialize(**data_manager.storage_details())) loop.run_until_complete(edit_instance(red, cli_flags)) loop.run_until_complete(driver_cls.teardown()) except (KeyboardInterrupt, EOFError): print("Aborted!") finally: loop.run_until_complete(asyncio.sleep(1)) asyncio.set_event_loop(None) loop.stop() loop.close() sys.exit(0)
async def do_migration( current_backend: BackendType, target_backend: BackendType ) -> Dict[str, Any]: cur_driver_cls = drivers.get_driver_class(current_backend) new_driver_cls = drivers.get_driver_class(target_backend) cur_storage_details = data_manager.storage_details() new_storage_details = new_driver_cls.get_config_details() await cur_driver_cls.initialize(**cur_storage_details) await new_driver_cls.initialize(**new_storage_details) await config.migrate(cur_driver_cls, new_driver_cls) await cur_driver_cls.teardown() await new_driver_cls.teardown() return new_storage_details
async def _setup_driver(): backend_type = _get_backend_type() storage_details = {} data_manager.storage_type = lambda: backend_type.value data_manager.storage_details = lambda: storage_details driver_cls = drivers.get_driver_class(backend_type) await driver_cls.initialize(**storage_details) yield await driver_cls.teardown()
async def create_backup( instance: str, destination_folder: Path = Path.home()) -> None: data_manager.load_basic_configuration(instance) backend_type = get_current_backend(instance) if backend_type != BackendType.JSON: await do_migration(backend_type, BackendType.JSON) print("Backing up the instance's data...") driver_cls = drivers.get_driver_class() await driver_cls.initialize(**data_manager.storage_details()) backup_fpath = await red_create_backup(destination_folder) await driver_cls.teardown() if backup_fpath is not None: print(f"A backup of {instance} has been made. It is at {backup_fpath}") else: print("Creating the backup failed.")
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))
def basic_setup(): """ Creates the data storage folder. :return: """ print( "Hello! Before we begin, we need to gather some initial information for the new instance." ) name = get_name() default_data_dir = get_data_dir(name) default_dirs = deepcopy(data_manager.basic_config_default) default_dirs["DATA_PATH"] = default_data_dir storage = get_storage_type() storage_dict = {1: BackendType.JSON, 2: BackendType.POSTGRES} storage_type: BackendType = storage_dict.get(storage, BackendType.JSON) default_dirs["STORAGE_TYPE"] = storage_type.value driver_cls = drivers.get_driver_class(storage_type) default_dirs["STORAGE_DETAILS"] = driver_cls.get_config_details() if name in instance_data: print( "WARNING: An instance already exists with this name. " "Continuing will overwrite the existing instance config." ) if not click.confirm("Are you absolutely certain you want to continue?", default=False): print("Not continuing") sys.exit(0) save_config(name, default_dirs) print() print( "Your basic configuration has been saved. Please run `redbot <name>` to" " continue your setup process and to run the bot.\n\n" "First time? Read the quickstart guide:\n" "https://docs.discord.red/en/stable/getting_started.html" )
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))
from redbot.core import data_manager, drivers from redbot.core.drivers import BackendType from redbot.setup import appdir, save_config name = os.getenv("REDBOT_NAME", "redbot") storage_type_value = os.getenv("REDBOT_STORAGE_TYPE", BackendType.JSON.value) storage_type: BackendType = BackendType[storage_type_value] data_path = Path(appdir.user_data_dir) / "data" / name if not data_path.exists(): data_path.mkdir(parents=True, exist_ok=True) default_data_dir = str(data_path.resolve()) driver_cls = drivers.get_driver_class(storage_type) default_dirs = deepcopy(data_manager.basic_config_default) default_dirs["DATA_PATH"] = default_data_dir default_dirs["STORAGE_TYPE"] = storage_type.value default_dirs["STORAGE_DETAILS"] = {} if storage_type == BackendType.POSTGRES \ or storage_type == storage_type.MONGO \ or storage_type == storage_type.MONGOV1: host = os.getenv("REDBOT_DB_HOST") port = os.getenv("REDBOT_DB_PORT") user = os.getenv("REDBOT_DB_USER") password = os.getenv("REDBOT_DB_PASS") database = os.getenv("REDBOT_DB_NAME")
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
def basic_setup( *, name: str, data_path: Optional[Path], backend: Optional[str], interactive: bool, overwrite_existing_instance: bool, ): """ Creates the data storage folder. :return: """ if not interactive and not name: print("Providing instance name through --instance-name is required" " when using non-interactive mode.") sys.exit(1) if interactive: print( "Hello! Before we begin, we need to gather some initial information" " for the new instance.") name = get_name(name) default_data_dir = get_data_dir(instance_name=name, data_path=data_path, interactive=interactive) default_dirs = deepcopy(data_manager.basic_config_default) default_dirs["DATA_PATH"] = default_data_dir storage_type = get_storage_type(backend, interactive=interactive) default_dirs["STORAGE_TYPE"] = storage_type.value driver_cls = drivers.get_driver_class(storage_type) default_dirs["STORAGE_DETAILS"] = driver_cls.get_config_details() if name in instance_data: if overwrite_existing_instance: pass elif interactive: print("WARNING: An instance already exists with this name. " "Continuing will overwrite the existing instance config.") if not click.confirm( "Are you absolutely certain you want to continue?", default=False): print("Not continuing") sys.exit(0) else: 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.") sys.exit(1) save_config(name, default_dirs) if interactive: print() print( f"Your basic configuration has been saved. Please run `redbot {name}` to" " continue your setup process and to run the bot.\n\n" "First time? Read the quickstart guide:\n" "https://docs.discord.red/en/stable/getting_started.html") else: print("Your basic configuration has been saved.")
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
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
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.")