aboutsummaryrefslogtreecommitdiffstats
path: root/bot/bot.py
diff options
context:
space:
mode:
authorGravatar William Da Silva <[email protected]>2020-12-29 01:15:03 -0500
committerGravatar GitHub <[email protected]>2020-12-29 01:15:03 -0500
commit52d626131527dfae3c100aab9a530bae928b0fff (patch)
tree8b09d5e4579924d8f1c7b9917cb2f04fdd54c29c /bot/bot.py
parentCapitalize "ID" in error message (diff)
parentMerge 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.py162
1 files changed, 53 insertions, 109 deletions
diff --git a/bot/bot.py b/bot/bot.py
index b486201e..112c9a48 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -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,
)