async def on_ready(): if bot._uptime is not None: return bot._uptime = datetime.utcnow() guilds = len(bot.guilds) users = len(set([m for m in bot.get_all_members()])) invite_url = discord.utils.oauth_url(bot._app_info.id) prefixes = cli_flags.prefix or (await bot._config.prefix()) lang = await bot._config.locale() red_pkg = pkg_resources.get_distribution("Red-DiscordBot") dpy_version = discord.__version__ table_general_info = Table(show_edge=False, show_header=False, box=box.MINIMAL) table_general_info.add_row("Prefixes", ", ".join(prefixes)) table_general_info.add_row("Language", lang) table_general_info.add_row("Red version", red_version) table_general_info.add_row("Discord.py version", dpy_version) table_general_info.add_row("Storage type", data_manager.storage_type()) table_counts = Table(show_edge=False, show_header=False, box=box.MINIMAL) # String conversion is needed as Rich doesn't deal with ints table_counts.add_row("Shards", str(bot.shard_count)) table_counts.add_row("Servers", str(guilds)) if bot.intents.members: # Lets avoid 0 Unique Users table_counts.add_row("Unique Users", str(users)) outdated_red_message = "" rich_outdated_message = "" with contextlib.suppress(aiohttp.ClientError, asyncio.TimeoutError): pypi_version, py_version_req = await fetch_latest_red_version_info( ) outdated = pypi_version and pypi_version > red_version_info if outdated: outdated_red_message = _( "Your Red instance is out of date! {} is the current " "version, however you are using {}!").format( pypi_version, red_version) rich_outdated_message = ( f"[red]Outdated version![/red]\n" f"[red]!!![/red]Version [cyan]{pypi_version}[/] is available, " f"but you're using [cyan]{red_version}[/][red]!!![/red]") current_python = platform.python_version() extra_update = _( "\n\nWhile the following command should work in most scenarios as it is " "based on your current OS, environment, and Python version, " "**we highly recommend you to read the update docs at <{docs}> and " "make sure there is nothing else that " "needs to be done during the update.**" ).format( docs="https://docs.discord.red/en/stable/update_red.html") if expected_version(current_python, py_version_req): installed_extras = [] for extra, reqs in red_pkg._dep_map.items(): if extra is None or extra in {"dev", "all"}: continue try: pkg_resources.require(req.name for req in reqs) except pkg_resources.DistributionNotFound: pass else: installed_extras.append(extra) if installed_extras: package_extras = f"[{','.join(installed_extras)}]" else: package_extras = "" extra_update += _( "\n\nTo update your bot, first shutdown your " "bot then open a window of {console} (Not as admin) and " "run the following:\n\n").format( console=_("Command Prompt") if platform.system() == "Windows" else _("Terminal")) extra_update += ( '```"{python}" -m pip install -U Red-DiscordBot{package_extras}```' .format(python=sys.executable, package_extras=package_extras)) extra_update += _( "\nOnce you've started up your bot again, if you have any 3rd-party cogs" " installed we then highly recommend you update them with this command" " in Discord: `[p]cog update`") else: extra_update += _( "\n\nYou have Python `{py_version}` and this update " "requires `{req_py}`; you cannot simply run the update command.\n\n" "You will need to follow the update instructions in our docs above, " "if you still need help updating after following the docs go to our " "#support channel in <https://discord.gg/red>").format( py_version=current_python, req_py=py_version_req) outdated_red_message += extra_update rich_console = rich.get_console() rich_console.print(INTRO, style="red", markup=False, highlight=False) if guilds: rich_console.print( Columns( [ Panel(table_general_info, title=str(bot.user.name)), Panel(table_counts) ], equal=True, align="center", )) else: rich_console.print( Columns([Panel(table_general_info, title=str(bot.user.name))])) rich_console.print("Loaded {} cogs with {} commands".format( len(bot.cogs), len(bot.commands))) if invite_url: rich_console.print( f"\nInvite URL: {Text(invite_url, style=f'link {invite_url}')}" ) # We generally shouldn't care if the client supports it or not as Rich deals with it. if not guilds: rich_console.print( f"Looking for a quick guide on setting up Red? Checkout {Text('https://start.discord.red', style='link https://start.discord.red}')}" ) if rich_outdated_message: rich_console.print(rich_outdated_message) bot._color = discord.Colour(await bot._config.color()) bot._red_ready.set() if outdated_red_message: await send_to_owners_with_prefix_replaced(bot, outdated_red_message)
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 run_bot(red: Red, cli_flags: Namespace) -> None: """ This runs the bot. Any shutdown which is a result of not being able to log in needs to raise a SystemExit exception. If the bot starts normally, the bot should be left to handle the exit case. It will raise SystemExit in a task, which will reach the event loop and interrupt running forever, then trigger our cleanup process, and does not need additional handling in this function. """ driver_cls = drivers.get_driver_class() await driver_cls.initialize(**data_manager.storage_details()) redbot.logging.init_logging(level=cli_flags.logging_level, location=data_manager.core_data_path() / "logs") log.debug("====Basic Config====") log.debug("Data Path: %s", data_manager._base_data_path()) log.debug("Storage Type: %s", data_manager.storage_type()) # lib folder has to be in sys.path before trying to load any 3rd-party cog (GH-3061) # We might want to change handling of requirements in Downloader at later date LIB_PATH = data_manager.cog_data_path(raw_name="Downloader") / "lib" LIB_PATH.mkdir(parents=True, exist_ok=True) if str(LIB_PATH) not in sys.path: sys.path.append(str(LIB_PATH)) # "It's important to note that the global `working_set` object is initialized from # `sys.path` when `pkg_resources` is first imported, but is only updated if you do # all future `sys.path` manipulation via `pkg_resources` APIs. If you manually modify # `sys.path`, you must invoke the appropriate methods on the `working_set` instance # to keep it in sync." # Source: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#workingset-objects pkg_resources.working_set.add_entry(str(LIB_PATH)) sys.meta_path.insert(0, SharedLibImportWarner()) if cli_flags.token: token = cli_flags.token else: token = os.environ.get("RED_TOKEN", None) if not token: token = await red._config.token() prefix = cli_flags.prefix or await red._config.prefix() if not (token and prefix): if cli_flags.no_prompt is False: new_token = await interactive_config(red, token_set=bool(token), prefix_set=bool(prefix)) if new_token: token = new_token else: log.critical("Token and prefix must be set in order to login.") sys.exit(1) if cli_flags.dry_run: await red.http.close() sys.exit(0) try: await red.start(token, bot=True, cli_flags=cli_flags) except discord.LoginFailure: log.critical("This token doesn't seem to be valid.") db_token = await red._config.token() if db_token and not cli_flags.no_prompt: if confirm("\nDo you want to reset the token?"): await red._config.token.set("") print("Token has been reset.") sys.exit(0) sys.exit(1) except discord.PrivilegedIntentsRequired: print( "Red requires all Privileged Intents to be enabled.\n" "You can find out how to enable Privileged Intents with this guide:\n" "https://docs.discord.red/en/stable/bot_application_guide.html#enabling-privileged-intents" ) sys.exit(1) return None
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 on_ready(): if bot._uptime is not None: return bot._uptime = datetime.utcnow() guilds = len(bot.guilds) users = len(set([m for m in bot.get_all_members()])) app_info = await bot.application_info() if app_info.team: if bot._use_team_features: bot.owner_ids.update(m.id for m in app_info.team.members) elif bot._owner_id_overwrite is None: bot.owner_ids.add(app_info.owner.id) bot._app_owners_fetched = True try: invite_url = discord.utils.oauth_url(app_info.id) except: invite_url = "Could not fetch invite url" prefixes = cli_flags.prefix or (await bot._config.prefix()) lang = await bot._config.locale() red_pkg = pkg_resources.get_distribution("Red-DiscordBot") dpy_version = discord.__version__ INFO = [ str(bot.user), "Prefixes: {}".format(", ".join(prefixes)), "Language: {}".format(lang), "Red Bot Version: {}".format(red_version), "Discord.py Version: {}".format(dpy_version), "Shards: {}".format(bot.shard_count), "Storage type: {}".format(data_manager.storage_type()), ] if guilds: INFO.extend(("Servers: {}".format(guilds), "Users: {}".format(users))) else: print("Ready. I'm not in any server yet!") INFO.append("{} cogs with {} commands".format(len(bot.cogs), len(bot.commands))) outdated_red_message = "" with contextlib.suppress(aiohttp.ClientError, asyncio.TimeoutError): pypi_version, py_version_req = await fetch_latest_red_version_info() outdated = pypi_version and pypi_version > red_version_info if outdated: INFO.append( "Outdated version! {} is available " "but you're using {}".format(pypi_version, red_version) ) outdated_red_message = _( "Your Red instance is out of date! {} is the current " "version, however you are using {}!" ).format(pypi_version, red_version) current_python = platform.python_version() extra_update = _( "\n\nWhile the following command should work in most scenarios as it is " "based on your current OS, environment, and Python version, " "**we highly recommend you to read the update docs at <{docs}> and " "make sure there is nothing else that " "needs to be done during the update.**" ).format(docs="https://docs.discord.red/en/stable/update_red.html") if expected_version(current_python, py_version_req): installed_extras = [] for extra, reqs in red_pkg._dep_map.items(): if extra is None or extra in {"dev", "all"}: continue try: pkg_resources.require(req.name for req in reqs) except pkg_resources.DistributionNotFound: pass else: installed_extras.append(extra) if installed_extras: package_extras = f"[{','.join(installed_extras)}]" else: package_extras = "" extra_update += _( "\n\nTo update your bot, first shutdown your " "bot then open a window of {console} (Not as admin) and " "run the following:\n\n" ).format( console=_("Command Prompt") if platform.system() == "Windows" else _("Terminal") ) extra_update += ( '```"{python}" -m pip install -U Red-DiscordBot{package_extras}```'.format( python=sys.executable, package_extras=package_extras ) ) else: extra_update += _( "\n\nYou have Python `{py_version}` and this update " "requires `{req_py}`; you cannot simply run the update command.\n\n" "You will need to follow the update instructions in our docs above, " "if you still need help updating after following the docs go to our " "#support channel in <https://discord.gg/red>" ).format(py_version=current_python, req_py=py_version_req) outdated_red_message += extra_update INFO2 = [] reqs_installed = {"docs": None, "test": None} for key in reqs_installed.keys(): reqs = [x.name for x in red_pkg._dep_map[key]] try: pkg_resources.require(reqs) except DistributionNotFound: reqs_installed[key] = False else: reqs_installed[key] = True options = ( ("Voice", True), ("Docs", reqs_installed["docs"]), ("Tests", reqs_installed["test"]), ) on_symbol, off_symbol, ascii_border = _get_startup_screen_specs() for option, enabled in options: enabled = on_symbol if enabled else off_symbol INFO2.append("{} {}".format(enabled, option)) print(Fore.RED + INTRO) print(Style.RESET_ALL) print(bordered(INFO, INFO2, ascii_border=ascii_border)) if invite_url: print("\nInvite URL: {}\n".format(invite_url)) if not guilds: print( "Looking for a quick guide on setting up Red? https://docs.discord.red/en/stable/getting_started.html\n" ) if not bot.owner_ids: # we could possibly exit here in future log.warning("Bot doesn't have any owner set!") bot._color = discord.Colour(await bot._config.color()) bot._red_ready.set() if outdated_red_message: await bot.send_to_owners(outdated_red_message)
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.")