aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ks129 <[email protected]>2021-01-06 09:35:58 +0200
committerGravatar ks129 <[email protected]>2021-01-06 09:35:58 +0200
commit1496557b757b6cea5448110ccd5143e5a5e82e61 (patch)
tree1f2518fab45d6d7a958de2f6d9269c13d60ed25a
parentRemove sir lancebot names from seasons (diff)
Refactor branding manager to keep everything in one directory
To keep everything at one place, moved all branding manager special things to one module.
-rw-r--r--bot/constants.py29
-rw-r--r--bot/decorators.py23
-rw-r--r--bot/errors.py4
-rw-r--r--bot/exts/backend/branding/__init__.py7
-rw-r--r--bot/exts/backend/branding/_cog.py (renamed from bot/exts/backend/branding.py)101
-rw-r--r--bot/exts/backend/branding/_constants.py49
-rw-r--r--bot/exts/backend/branding/_decorators.py27
-rw-r--r--bot/exts/backend/branding/_errors.py2
-rw-r--r--bot/exts/backend/branding/_seasons.py (renamed from bot/seasons.py)5
9 files changed, 135 insertions, 112 deletions
diff --git a/bot/constants.py b/bot/constants.py
index ded6e386d..41a538802 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -651,35 +651,6 @@ class Event(Enum):
voice_state_update = "voice_state_update"
-class Month(IntEnum):
- JANUARY = 1
- FEBRUARY = 2
- MARCH = 3
- APRIL = 4
- MAY = 5
- JUNE = 6
- JULY = 7
- AUGUST = 8
- SEPTEMBER = 9
- OCTOBER = 10
- NOVEMBER = 11
- DECEMBER = 12
-
- def __str__(self) -> str:
- return self.name.title()
-
-
-class AssetType(Enum):
- """
- Discord media assets.
-
- The values match exactly the kwarg keys that can be passed to `Guild.edit`.
- """
-
- BANNER = "banner"
- SERVER_ICON = "icon"
-
-
# Debug mode
DEBUG_MODE = 'local' in os.environ.get("SITE_URL", "local")
diff --git a/bot/decorators.py b/bot/decorators.py
index 0b50cc365..063c8f878 100644
--- a/bot/decorators.py
+++ b/bot/decorators.py
@@ -1,5 +1,4 @@
import asyncio
-import functools
import logging
import typing as t
from contextlib import suppress
@@ -9,7 +8,7 @@ from discord import Member, NotFound
from discord.ext import commands
from discord.ext.commands import Cog, Context
-from bot.constants import Channels, DEBUG_MODE, RedirectOutput
+from bot.constants import Channels, RedirectOutput
from bot.utils import function
from bot.utils.checks import in_whitelist_check
@@ -154,23 +153,3 @@ def respect_role_hierarchy(member_arg: function.Argument) -> t.Callable:
await func(*args, **kwargs)
return wrapper
return decorator
-
-
-def mock_in_debug(return_value: t.Any) -> t.Callable:
- """
- Short-circuit function execution if in debug mode and return `return_value`.
-
- The original function name, and the incoming args and kwargs are DEBUG level logged
- upon each call. This is useful for expensive operations, i.e. media asset uploads
- that are prone to rate-limits but need to be tested extensively.
- """
- def decorator(func: t.Callable) -> t.Callable:
- @functools.wraps(func)
- async def wrapped(*args, **kwargs) -> t.Any:
- """Short-circuit and log if in debug mode."""
- if DEBUG_MODE:
- log.debug(f"Function {func.__name__} called with args: {args}, kwargs: {kwargs}")
- return return_value
- return await func(*args, **kwargs)
- return wrapped
- return decorator
diff --git a/bot/errors.py b/bot/errors.py
index ea6fb36ec..65d715203 100644
--- a/bot/errors.py
+++ b/bot/errors.py
@@ -18,7 +18,3 @@ class LockedResourceError(RuntimeError):
f"Cannot operate on {self.type.lower()} `{self.id}`; "
"it is currently locked and in use by another operation."
)
-
-
-class BrandingError(Exception):
- """Exception raised by the BrandingManager cog."""
diff --git a/bot/exts/backend/branding/__init__.py b/bot/exts/backend/branding/__init__.py
new file mode 100644
index 000000000..81ea3bf49
--- /dev/null
+++ b/bot/exts/backend/branding/__init__.py
@@ -0,0 +1,7 @@
+from bot.bot import Bot
+from bot.exts.backend.branding._cog import BrandingManager
+
+
+def setup(bot: Bot) -> None:
+ """Loads BrandingManager cog."""
+ bot.add_cog(BrandingManager(bot))
diff --git a/bot/exts/backend/branding.py b/bot/exts/backend/branding/_cog.py
index 7ce85aab2..d7fa78bb5 100644
--- a/bot/exts/backend/branding.py
+++ b/bot/exts/backend/branding/_cog.py
@@ -12,29 +12,12 @@ from async_rediscache import RedisCache
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import AssetType, Branding, Colours, Emojis, Guild, Keys, MODERATION_ROLES
-from bot.decorators import in_whitelist, mock_in_debug
-from bot.errors import BrandingError
-from bot.seasons import SeasonBase, get_all_seasons, get_current_season, get_season
+from bot.constants import Branding, Colours, Emojis, Guild, MODERATION_ROLES
+from bot.decorators import in_whitelist
+from bot.exts.backend.branding import _constants, _decorators, _errors, _seasons
log = logging.getLogger(__name__)
-STATUS_OK = 200 # HTTP status code
-
-FILE_BANNER = "banner.png"
-FILE_AVATAR = "avatar.png"
-SERVER_ICONS = "server_icons"
-
-BRANDING_URL = "https://api.github.com/repos/python-discord/branding/contents"
-
-PARAMS = {"ref": "master"} # Target branch
-HEADERS = {"Accept": "application/vnd.github.v3+json"} # Ensure we use API v3
-
-# A GitHub token is not necessary for the cog to operate,
-# unauthorized requests are however limited to 60 per hour
-if Keys.github:
- HEADERS["Authorization"] = f"token {Keys.github}"
-
class GitHubFile(t.NamedTuple):
"""
@@ -120,7 +103,7 @@ class BrandingManager(commands.Cog):
to test this cog's behaviour.
"""
- current_season: t.Type[SeasonBase]
+ current_season: t.Type[_seasons.SeasonBase]
banner: t.Optional[GitHubFile]
@@ -143,7 +126,7 @@ class BrandingManager(commands.Cog):
the `refresh` command is used.
"""
self.bot = bot
- self.current_season = get_current_season()
+ self.current_season = _seasons.get_current_season()
self.banner = None
@@ -183,7 +166,7 @@ class BrandingManager(commands.Cog):
await self.bot.wait_until_guild_available()
while True:
- self.current_season = get_current_season()
+ self.current_season = _seasons.get_current_season()
branding_changed = await self.refresh()
if branding_changed:
@@ -200,7 +183,7 @@ class BrandingManager(commands.Cog):
info_embed = discord.Embed(description=self.current_season.description, colour=self.current_season.colour)
# If we're in a non-evergreen season, also show active months
- if self.current_season is not SeasonBase:
+ if self.current_season is not _seasons.SeasonBase:
title = f"{self.current_season.season_name} ({', '.join(str(m) for m in self.current_season.months)})"
else:
title = self.current_season.season_name
@@ -252,10 +235,12 @@ class BrandingManager(commands.Cog):
This may return an empty dict if the response status is non-200,
or if the target directory is empty.
"""
- url = f"{BRANDING_URL}/{path}"
- async with self.bot.http_session.get(url, headers=HEADERS, params=PARAMS) as resp:
+ url = f"{_constants.BRANDING_URL}/{path}"
+ async with self.bot.http_session.get(
+ url, headers=_constants.HEADERS, params=_constants.PARAMS
+ ) as resp:
# Short-circuit if we get non-200 response
- if resp.status != STATUS_OK:
+ if resp.status != _constants.STATUS_OK:
log.error(f"GitHub API returned non-200 response: {resp}")
return {}
directory = await resp.json() # Directory at `path`
@@ -287,23 +272,32 @@ class BrandingManager(commands.Cog):
# Only make a call to the fallback directory if there is something to be gained
branding_incomplete = any(
asset not in seasonal_dir
- for asset in (FILE_BANNER, FILE_AVATAR, SERVER_ICONS)
+ for asset in (_constants.FILE_BANNER, _constants.FILE_AVATAR, _constants.SERVER_ICONS)
)
- if branding_incomplete and self.current_season is not SeasonBase:
- fallback_dir = await self._get_files(SeasonBase.branding_path, include_dirs=True)
+ if branding_incomplete and self.current_season is not _seasons.SeasonBase:
+ fallback_dir = await self._get_files(
+ _seasons.SeasonBase.branding_path, include_dirs=True
+ )
else:
fallback_dir = {}
# Resolve assets in this directory, None is a safe value
- self.banner = seasonal_dir.get(FILE_BANNER) or fallback_dir.get(FILE_BANNER)
+ self.banner = (
+ seasonal_dir.get(_constants.FILE_BANNER)
+ or fallback_dir.get(_constants.FILE_BANNER)
+ )
# Now resolve server icons by making a call to the proper sub-directory
- if SERVER_ICONS in seasonal_dir:
- icons_dir = await self._get_files(f"{self.current_season.branding_path}/{SERVER_ICONS}")
+ if _constants.SERVER_ICONS in seasonal_dir:
+ icons_dir = await self._get_files(
+ f"{self.current_season.branding_path}/{_constants.SERVER_ICONS}"
+ )
self.available_icons = list(icons_dir.values())
- elif SERVER_ICONS in fallback_dir:
- icons_dir = await self._get_files(f"{SeasonBase.branding_path}/{SERVER_ICONS}")
+ elif _constants.SERVER_ICONS in fallback_dir:
+ icons_dir = await self._get_files(
+ f"{_seasons.SeasonBase.branding_path}/{_constants.SERVER_ICONS}"
+ )
self.available_icons = list(icons_dir.values())
else:
@@ -373,8 +367,8 @@ class BrandingManager(commands.Cog):
"""List all available seasons and branding sources."""
embed = discord.Embed(title="Available seasons", colour=Colours.soft_green)
- for season in get_all_seasons():
- if season is SeasonBase:
+ for season in _seasons.get_all_seasons():
+ if season is _seasons.SeasonBase:
active_when = "always"
else:
active_when = f"in {', '.join(str(m) for m in season.months)}"
@@ -407,14 +401,14 @@ class BrandingManager(commands.Cog):
what it should be - the daemon will make sure that it's set back properly.
"""
if season_name is None:
- new_season = get_current_season()
+ new_season = _seasons.get_current_season()
else:
- new_season = get_season(season_name)
+ new_season = _seasons.get_season(season_name)
if new_season is None:
- raise BrandingError("No such season exists")
+ raise _errors.BrandingError("No such season exists")
if self.current_season is new_season:
- raise BrandingError(f"Season {self.current_season.season_name} already active")
+ raise _errors.BrandingError(f"Season {self.current_season.season_name} already active")
self.current_season = new_season
await self.branding_refresh(ctx)
@@ -447,7 +441,9 @@ class BrandingManager(commands.Cog):
async with ctx.typing():
failed_assets = await self.apply()
if failed_assets:
- raise BrandingError(f"Failed to apply following assets: {', '.join(failed_assets)}")
+ raise _errors.BrandingError(
+ f"Failed to apply following assets: {', '.join(failed_assets)}"
+ )
response = discord.Embed(description=f"All assets applied {Emojis.ok_hand}", colour=Colours.soft_green)
await ctx.send(embed=response)
@@ -462,7 +458,7 @@ class BrandingManager(commands.Cog):
async with ctx.typing():
success = await self.cycle()
if not success:
- raise BrandingError("Failed to cycle icon")
+ raise _errors.BrandingError("Failed to cycle icon")
response = discord.Embed(description=f"Success {Emojis.ok_hand}", colour=Colours.soft_green)
await ctx.send(embed=response)
@@ -489,7 +485,7 @@ class BrandingManager(commands.Cog):
async def daemon_start(self, ctx: commands.Context) -> None:
"""If the daemon isn't running, start it."""
if self._daemon_running:
- raise BrandingError("Daemon already running!")
+ raise _errors.BrandingError("Daemon already running!")
self.daemon = self.bot.loop.create_task(self._daemon_func())
await self.branding_configuration.set("daemon_active", True)
@@ -501,7 +497,7 @@ class BrandingManager(commands.Cog):
async def daemon_stop(self, ctx: commands.Context) -> None:
"""If the daemon is running, stop it."""
if not self._daemon_running:
- raise BrandingError("Daemon not running!")
+ raise _errors.BrandingError("Daemon not running!")
self.daemon.cancel()
await self.branding_configuration.set("daemon_active", False)
@@ -515,7 +511,7 @@ class BrandingManager(commands.Cog):
async with self.bot.http_session.get(url) as resp:
return await resp.read()
- async def _apply_asset(self, target: discord.Guild, asset: AssetType, url: str) -> bool:
+ async def _apply_asset(self, target: discord.Guild, asset: _constants.AssetType, url: str) -> bool:
"""
Internal method for applying media assets to the guild.
@@ -543,7 +539,7 @@ class BrandingManager(commands.Cog):
log.info("Asset successfully applied")
return True
- @mock_in_debug(return_value=True)
+ @_decorators.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.bot.get_guild(Guild.id)
@@ -551,9 +547,9 @@ class BrandingManager(commands.Cog):
log.info("Failed to get guild instance, aborting asset upload")
return False
- return await self._apply_asset(guild, AssetType.BANNER, url)
+ return await self._apply_asset(guild, _constants.AssetType.BANNER, url)
- @mock_in_debug(return_value=True)
+ @_decorators.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.bot.get_guild(Guild.id)
@@ -561,9 +557,4 @@ class BrandingManager(commands.Cog):
log.info("Failed to get guild instance, aborting asset upload")
return False
- return await self._apply_asset(guild, AssetType.SERVER_ICON, url)
-
-
-def setup(bot: Bot) -> None:
- """Load BrandingManager cog."""
- bot.add_cog(BrandingManager(bot))
+ return await self._apply_asset(guild, _constants.AssetType.SERVER_ICON, url)
diff --git a/bot/exts/backend/branding/_constants.py b/bot/exts/backend/branding/_constants.py
new file mode 100644
index 000000000..f4c815fbd
--- /dev/null
+++ b/bot/exts/backend/branding/_constants.py
@@ -0,0 +1,49 @@
+from enum import Enum, IntEnum
+
+from bot.constants import Keys
+
+
+class Month(IntEnum):
+ JANUARY = 1
+ FEBRUARY = 2
+ MARCH = 3
+ APRIL = 4
+ MAY = 5
+ JUNE = 6
+ JULY = 7
+ AUGUST = 8
+ SEPTEMBER = 9
+ OCTOBER = 10
+ NOVEMBER = 11
+ DECEMBER = 12
+
+ def __str__(self) -> str:
+ return self.name.title()
+
+
+class AssetType(Enum):
+ """
+ Discord media assets.
+
+ The values match exactly the kwarg keys that can be passed to `Guild.edit`.
+ """
+
+ BANNER = "banner"
+ SERVER_ICON = "icon"
+
+
+STATUS_OK = 200 # HTTP status code
+
+FILE_BANNER = "banner.png"
+FILE_AVATAR = "avatar.png"
+SERVER_ICONS = "server_icons"
+
+BRANDING_URL = "https://api.github.com/repos/python-discord/branding/contents"
+
+PARAMS = {"ref": "master"} # Target branch
+HEADERS = {"Accept": "application/vnd.github.v3+json"} # Ensure we use API v3
+
+# A GitHub token is not necessary for the cog to operate,
+# unauthorized requests are however limited to 60 per hour
+if Keys.github:
+ HEADERS["Authorization"] = f"token {Keys.github}"
diff --git a/bot/exts/backend/branding/_decorators.py b/bot/exts/backend/branding/_decorators.py
new file mode 100644
index 000000000..6a1e7e869
--- /dev/null
+++ b/bot/exts/backend/branding/_decorators.py
@@ -0,0 +1,27 @@
+import functools
+import logging
+import typing as t
+
+from bot.constants import DEBUG_MODE
+
+log = logging.getLogger(__name__)
+
+
+def mock_in_debug(return_value: t.Any) -> t.Callable:
+ """
+ Short-circuit function execution if in debug mode and return `return_value`.
+
+ The original function name, and the incoming args and kwargs are DEBUG level logged
+ upon each call. This is useful for expensive operations, i.e. media asset uploads
+ that are prone to rate-limits but need to be tested extensively.
+ """
+ def decorator(func: t.Callable) -> t.Callable:
+ @functools.wraps(func)
+ async def wrapped(*args, **kwargs) -> t.Any:
+ """Short-circuit and log if in debug mode."""
+ if DEBUG_MODE:
+ log.debug(f"Function {func.__name__} called with args: {args}, kwargs: {kwargs}")
+ return return_value
+ return await func(*args, **kwargs)
+ return wrapped
+ return decorator
diff --git a/bot/exts/backend/branding/_errors.py b/bot/exts/backend/branding/_errors.py
new file mode 100644
index 000000000..7cd271af3
--- /dev/null
+++ b/bot/exts/backend/branding/_errors.py
@@ -0,0 +1,2 @@
+class BrandingError(Exception):
+ """Exception raised by the BrandingManager cog."""
diff --git a/bot/seasons.py b/bot/exts/backend/branding/_seasons.py
index d4a9dfcc5..2f785bec0 100644
--- a/bot/seasons.py
+++ b/bot/exts/backend/branding/_seasons.py
@@ -2,8 +2,9 @@ import logging
import typing as t
from datetime import datetime
-from bot.constants import Colours, Month
-from bot.errors import BrandingError
+from ._constants import Month
+from ._errors import BrandingError
+from bot.constants import Colours
log = logging.getLogger(__name__)