aboutsummaryrefslogtreecommitdiffstats
path: root/bot
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2022-06-28 22:36:22 +0100
committerGravatar Chris Lovering <[email protected]>2022-09-21 23:02:55 +0100
commit510da5190a0f499397c9419fa3b125233bed566c (patch)
tree8d6cb2a7c66ae353f40ddc711aed13fd0c51b667 /bot
parentUse monkey patches from botcore (diff)
Use BotBase from bot core
Diffstat (limited to 'bot')
-rw-r--r--bot/__init__.py6
-rw-r--r--bot/__main__.py70
-rw-r--r--bot/bot.py166
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())
diff --git a/bot/bot.py b/bot/bot.py
index c7b87a65..221bfd62 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -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!")