diff options
author | 2020-12-29 01:15:03 -0500 | |
---|---|---|
committer | 2020-12-29 01:15:03 -0500 | |
commit | 52d626131527dfae3c100aab9a530bae928b0fff (patch) | |
tree | 8b09d5e4579924d8f1c7b9917cb2f04fdd54c29c /bot/bot.py | |
parent | Capitalize "ID" in error message (diff) | |
parent | Merge pull request #484 from WillDaSilva/omdb-to-tmdb (diff) |
Merge branch 'master' into startup-channel-check
Diffstat (limited to 'bot/bot.py')
-rw-r--r-- | bot/bot.py | 162 |
1 files changed, 53 insertions, 109 deletions
@@ -1,36 +1,22 @@ import asyncio -import enum import logging import socket -from typing import Optional, Union +from typing import Optional -import async_timeout import discord from aiohttp import AsyncResolver, ClientSession, TCPConnector -from discord import DiscordException, Embed, Guild, User +from async_rediscache import RedisSession +from discord import DiscordException, Embed from discord.ext import commands -from bot.constants import Channels, Client, MODERATION_ROLES -from bot.utils.decorators import mock_in_debug +from bot import constants log = logging.getLogger(__name__) -__all__ = ("AssetType", "SeasonalBot", "bot") +__all__ = ("Bot", "bot") -class AssetType(enum.Enum): - """ - Discord media assets. - - The values match exactly the kwarg keys that can be passed to `Guild.edit` or `User.edit`. - """ - - BANNER = "banner" - AVATAR = "avatar" - SERVER_ICON = "icon" - - -class SeasonalBot(commands.Bot): +class Bot(commands.Bot): """ Base bot instance. @@ -39,23 +25,37 @@ class SeasonalBot(commands.Bot): that the upload was successful. See the `mock_in_debug` decorator for further details. """ - def __init__(self, **kwargs): + 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("SeasonalBot", "Connected!")) + 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.""" - guild = self.get_guild(Client.guild) + guild = self.get_guild(constants.Client.guild) if not guild: return None return guild.me + 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`. @@ -72,83 +72,6 @@ class SeasonalBot(commands.Bot): else: await super().on_command_error(context, exception) - async def _fetch_image(self, url: str) -> bytes: - """Retrieve and read image from `url`.""" - log.debug(f"Getting image from: {url}") - async with self.http_session.get(url) as resp: - return await resp.read() - - async def _apply_asset(self, target: Union[Guild, User], asset: AssetType, url: str) -> bool: - """ - Internal method for applying media assets to the guild or the bot. - - This shouldn't be called directly. The purpose of this method is mainly generic - error handling to reduce needless code repetition. - - Return True if upload was successful, False otherwise. - """ - log.info(f"Attempting to set {asset.name}: {url}") - - kwargs = {asset.value: await self._fetch_image(url)} - try: - async with async_timeout.timeout(5): - await target.edit(**kwargs) - - except asyncio.TimeoutError: - log.info("Asset upload timed out") - return False - - except discord.HTTPException as discord_error: - log.exception("Asset upload failed", exc_info=discord_error) - return False - - else: - log.info("Asset successfully applied") - return True - - @mock_in_debug(return_value=True) - async def set_banner(self, url: str) -> bool: - """Set the guild's banner to image at `url`.""" - guild = self.get_guild(Client.guild) - if guild is None: - log.info("Failed to get guild instance, aborting asset upload") - return False - - return await self._apply_asset(guild, AssetType.BANNER, url) - - @mock_in_debug(return_value=True) - async def set_icon(self, url: str) -> bool: - """Sets the guild's icon to image at `url`.""" - guild = self.get_guild(Client.guild) - if guild is None: - log.info("Failed to get guild instance, aborting asset upload") - return False - - return await self._apply_asset(guild, AssetType.SERVER_ICON, url) - - @mock_in_debug(return_value=True) - async def set_avatar(self, url: str) -> bool: - """Set the bot's avatar to image at `url`.""" - return await self._apply_asset(self.user, AssetType.AVATAR, url) - - @mock_in_debug(return_value=True) - async def set_nickname(self, new_name: str) -> bool: - """Set the bot nickname in the main guild to `new_name`.""" - member = self.member - if member is None: - log.info("Failed to get bot member instance, aborting asset upload") - return False - - log.info(f"Attempting to set nickname to {new_name}") - try: - await member.edit(nick=new_name) - except discord.HTTPException as discord_error: - log.exception("Setting nickname failed", exc_info=discord_error) - return False - else: - log.info("Nickname set successfully") - return True - async def check_channels(self) -> None: """Verifies that all channel constants refer to channels which exist.""" await self.wait_until_guild_available() @@ -162,12 +85,12 @@ class SeasonalBot(commands.Bot): async def send_log(self, title: str, details: str = None, *, icon: str = None) -> None: """Send an embed message to the devlog channel.""" await self.wait_until_guild_available() - devlog = self.get_channel(Channels.devlog) + devlog = self.get_channel(constants.Channels.devlog) if not devlog: - log.info(f"Fetching devlog channel as it wasn't found in the cache (ID: {Channels.devlog})") + log.info(f"Fetching devlog channel as it wasn't found in the cache (ID: {constants.Channels.devlog})") try: - devlog = await self.fetch_channel(Channels.devlog) + devlog = await self.fetch_channel(constants.Channels.devlog) except discord.HTTPException as discord_exc: log.exception("Fetch failed", exc_info=discord_exc) return @@ -187,7 +110,7 @@ class SeasonalBot(commands.Bot): If the cache appears to still be empty (no members, no channels, or no roles), the event will not be set. """ - if guild.id != Client.guild: + if guild.id != constants.Client.guild: return if not guild.roles or not guild.members or not guild.channels: @@ -198,7 +121,7 @@ class SeasonalBot(commands.Bot): async def on_guild_unavailable(self, guild: discord.Guild) -> None: """Clear the internal `_guild_available` event when PyDis guild becomes unavailable.""" - if guild.id != Client.guild: + if guild.id != constants.Client.guild: return self._guild_available.clear() @@ -213,9 +136,30 @@ class SeasonalBot(commands.Bot): await self._guild_available.wait() -_allowed_roles = [discord.Object(id_) for id_ in MODERATION_ROLES] -bot = SeasonalBot( - command_prefix=Client.prefix, - activity=discord.Game(name=f"Commands: {Client.prefix}help"), +_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=constants.Client.prefix, + activity=discord.Game(name=f"Commands: {constants.Client.prefix}help"), allowed_mentions=discord.AllowedMentions(everyone=False, roles=_allowed_roles), + intents=_intents, ) |