diff options
author | 2022-06-28 22:36:22 +0100 | |
---|---|---|
committer | 2022-09-21 23:02:55 +0100 | |
commit | 510da5190a0f499397c9419fa3b125233bed566c (patch) | |
tree | 8d6cb2a7c66ae353f40ddc711aed13fd0c51b667 /bot | |
parent | Use monkey patches from botcore (diff) |
Use BotBase from bot core
Diffstat (limited to 'bot')
-rw-r--r-- | bot/__init__.py | 6 | ||||
-rw-r--r-- | bot/__main__.py | 70 | ||||
-rw-r--r-- | bot/bot.py | 166 |
3 files changed, 79 insertions, 163 deletions
diff --git a/bot/__init__.py b/bot/__init__.py index c1ecb30f..33fd4e1c 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -8,6 +8,7 @@ except ModuleNotFoundError: import asyncio import logging import os +from typing import TYPE_CHECKING import arrow import sentry_sdk @@ -17,6 +18,9 @@ from sentry_sdk.integrations.redis import RedisIntegration from bot import log +if TYPE_CHECKING: + from bot.bot import Bot + sentry_logging = LoggingIntegration( level=logging.DEBUG, event_level=logging.WARNING @@ -41,3 +45,5 @@ if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) apply_monkey_patches() + +instance: "Bot" = None # Global Bot instance. diff --git a/bot/__main__.py b/bot/__main__.py index bd6c70ee..418fd91b 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,19 +1,67 @@ +import asyncio import logging -from bot.bot import bot -from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS +import aiohttp +import discord +from async_rediscache import RedisSession +from botcore import StartupError +from discord.ext import commands + +import bot +from bot import constants +from bot.bot import Bot from bot.utils.decorators import whitelist_check -from bot.utils.extensions import walk_extensions log = logging.getLogger(__name__) -bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES)) -for ext in walk_extensions(): - bot.load_extension(ext) +async def _create_redis_session() -> RedisSession: + """Create and connect to a redis session.""" + redis_session = RedisSession( + address=(constants.RedisConfig.host, constants.RedisConfig.port), + password=constants.RedisConfig.password, + minsize=1, + maxsize=20, + use_fakeredis=constants.RedisConfig.use_fakeredis, + global_namespace="bot", + ) + try: + await redis_session.connect() + except OSError as e: + raise StartupError(e) + return redis_session + + +async def main() -> None: + """Entry async method for starting the bot.""" + allowed_roles = list({discord.Object(id_) for id_ in constants.MODERATION_ROLES}) + intents = discord.Intents.default() + intents.bans = False + intents.integrations = False + intents.invites = False + intents.message_content = True + intents.typing = False + intents.webhooks = False + + async with aiohttp.ClientSession() as session: + bot.instance = Bot( + guild_id=constants.Client.guild, + http_session=session, + redis_session=await _create_redis_session(), + command_prefix=commands.when_mentioned_or(constants.Client.prefix), + activity=discord.Game(name=f"Commands: {constants.Client.prefix}help"), + case_insensitive=True, + allowed_mentions=discord.AllowedMentions(everyone=False, roles=allowed_roles), + intents=intents, + allowed_roles=allowed_roles, + ) + + async with bot.instance as _bot: + _bot.add_check(whitelist_check( + channels=constants.WHITELISTED_CHANNELS, + roles=constants.STAFF_ROLES, + )) + await _bot.start(constants.Client.token) + -if not Client.in_ci: - # Manually enable the message content intent. This is required until the below PR is merged - # https://github.com/python-discord/sir-lancebot/pull/1092 - bot._connection._intents.value += 1 << 15 - bot.run(Client.token) +asyncio.run(main()) @@ -1,24 +1,20 @@ -import asyncio import logging -import socket -from contextlib import suppress from typing import Optional import discord -from aiohttp import AsyncResolver, ClientSession, TCPConnector -from async_rediscache import RedisSession -from discord import DiscordException, Embed, Forbidden, Thread +from botcore import BotBase +from botcore.utils import scheduling +from discord import DiscordException, Embed from discord.ext import commands -from discord.ext.commands import Cog, when_mentioned_or -from bot import constants +from bot import constants, exts log = logging.getLogger(__name__) -__all__ = ("Bot", "bot") +__all__ = ("Bot", ) -class Bot(commands.Bot): +class Bot(BotBase): """ Base bot instance. @@ -29,16 +25,6 @@ class Bot(commands.Bot): name = constants.Client.name - def __init__(self, redis_session: RedisSession, **kwargs): - super().__init__(**kwargs) - self.http_session = ClientSession( - connector=TCPConnector(resolver=AsyncResolver(), family=socket.AF_INET) - ) - self._guild_available = asyncio.Event() - self.redis_session = redis_session - self.loop.create_task(self.check_channels()) - self.loop.create_task(self.send_log(self.name, "Connected!")) - @property def member(self) -> Optional[discord.Member]: """Retrieves the guild member object for the bot.""" @@ -47,60 +33,6 @@ class Bot(commands.Bot): return None return guild.me - @Cog.listener() - async def on_thread_join(self, thread: Thread) -> None: - """ - Try to join newly created threads. - - Despite the event name being misleading, this is dispatched when new threads are created. - We want our bots to automatically join threads in order to answer commands using their prefixes. - """ - if thread.me: - # Already in this thread, return early - return - - with suppress(Forbidden): - await thread.join() - - async def close(self) -> None: - """Close Redis session when bot is shutting down.""" - await super().close() - - if self.http_session: - await self.http_session.close() - - if self.redis_session: - await self.redis_session.close() - - def add_cog(self, cog: commands.Cog) -> None: - """ - Delegate to super to register `cog`. - - This only serves to make the info log, so that extensions don't have to. - """ - super().add_cog(cog) - log.info(f"Cog loaded: {cog.qualified_name}") - - def add_command(self, command: commands.Command) -> None: - """Add `command` as normal and then add its root aliases to the bot.""" - super().add_command(command) - self._add_root_aliases(command) - - def remove_command(self, name: str) -> Optional[commands.Command]: - """ - Remove a command/alias as normal and then remove its root aliases from the bot. - - Individual root aliases cannot be removed by this function. - To remove them, either remove the entire command or manually edit `bot.all_commands`. - """ - command = super().remove_command(name) - if command is None: - # Even if it's a root alias, there's no way to get the Bot instance to remove the alias. - return - - self._remove_root_aliases(command) - return command - async def on_command_error(self, context: commands.Context, exception: DiscordException) -> None: """Check command errors for UserInputError and reset the cooldown if thrown.""" if isinstance(exception, commands.UserInputError): @@ -144,84 +76,14 @@ class Bot(commands.Bot): await devlog.send(embed=embed) - async def on_guild_available(self, guild: discord.Guild) -> None: - """ - Set the internal `_guild_available` event when PyDis guild becomes available. + async def setup_hook(self) -> None: + """Default async initialisation method for discord.py.""" + await super().setup_hook() - If the cache appears to still be empty (no members, no channels, or no roles), the event - will not be set. - """ - if guild.id != constants.Client.guild: - return - - if not guild.roles or not guild.members or not guild.channels: - log.warning("Guild available event was dispatched but the cache appears to still be empty!") - return + await self.check_channels() - self._guild_available.set() - - async def on_guild_unavailable(self, guild: discord.Guild) -> None: - """Clear the internal `_guild_available` event when PyDis guild becomes unavailable.""" - if guild.id != constants.Client.guild: - return + # This is not awaited to avoid a deadlock with any cogs that have + # wait_until_guild_available in their cog_load method. + scheduling.create_task(self.load_extensions(exts)) - self._guild_available.clear() - - async def wait_until_guild_available(self) -> None: - """ - Wait until the PyDis guild becomes available (and the cache is ready). - - The on_ready event is inadequate because it only waits 2 seconds for a GUILD_CREATE - gateway event before giving up and thus not populating the cache for unavailable guilds. - """ - await self._guild_available.wait() - - def _add_root_aliases(self, command: commands.Command) -> None: - """Recursively add root aliases for `command` and any of its subcommands.""" - if isinstance(command, commands.Group): - for subcommand in command.commands: - self._add_root_aliases(subcommand) - - for alias in getattr(command, "root_aliases", ()): - if alias in self.all_commands: - raise commands.CommandRegistrationError(alias, alias_conflict=True) - - self.all_commands[alias] = command - - def _remove_root_aliases(self, command: commands.Command) -> None: - """Recursively remove root aliases for `command` and any of its subcommands.""" - if isinstance(command, commands.Group): - for subcommand in command.commands: - self._remove_root_aliases(subcommand) - - for alias in getattr(command, "root_aliases", ()): - self.all_commands.pop(alias, None) - - -_allowed_roles = [discord.Object(id_) for id_ in constants.MODERATION_ROLES] - -_intents = discord.Intents.default() # Default is all intents except for privileged ones (Members, Presences, ...) -_intents.bans = False -_intents.integrations = False -_intents.invites = False -_intents.typing = False -_intents.webhooks = False - -redis_session = RedisSession( - address=(constants.RedisConfig.host, constants.RedisConfig.port), - password=constants.RedisConfig.password, - minsize=1, - maxsize=20, - use_fakeredis=constants.RedisConfig.use_fakeredis, - global_namespace="sir-lancebot" -) -loop = asyncio.get_event_loop() -loop.run_until_complete(redis_session.connect()) - -bot = Bot( - redis_session=redis_session, - command_prefix=when_mentioned_or(constants.Client.prefix), - activity=discord.Game(name=f"Commands: {constants.Client.prefix}help"), - allowed_mentions=discord.AllowedMentions(everyone=False, roles=_allowed_roles), - intents=_intents, -) + await self.send_log(self.name, "Connected!") |