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)
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
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 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
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))
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 {}
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)
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 {}
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
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.")