aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Boris Muratov <[email protected]>2023-04-11 17:35:14 +0300
committerGravatar GitHub <[email protected]>2023-04-11 14:35:14 +0000
commite94a53cbc97fa42340660953627a3593b5c191e9 (patch)
tree65d1b65891da1b0850fedd08938a09ddd3c96b4f
parentBump pytest from 7.2.2 to 7.3.0 (#2525) (diff)
Remove dead or unused code (#2528)
-rw-r--r--bot/constants.py11
-rw-r--r--bot/exts/events/__init__.py0
-rw-r--r--bot/exts/events/code_jams/__init__.py8
-rw-r--r--bot/exts/events/code_jams/_channels.py113
-rw-r--r--bot/exts/events/code_jams/_cog.py239
-rw-r--r--bot/rules/mentions.py70
-rw-r--r--tests/bot/exts/events/__init__.py0
-rw-r--r--tests/bot/exts/events/test_code_jams.py170
8 files changed, 0 insertions, 611 deletions
diff --git a/bot/constants.py b/bot/constants.py
index eefae11e2..4b3f6a6a7 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -326,17 +326,6 @@ class _Colours(EnvConfig):
Colours = _Colours()
-class _Free(EnvConfig):
- EnvConfig.Config.env_prefix = "free_"
-
- activity_timeout = 600
- cooldown_per = 60.0
- cooldown_rate = 1
-
-
-Free = _Free()
-
-
class _HelpChannels(EnvConfig):
EnvConfig.Config.env_prefix = "help_channels_"
diff --git a/bot/exts/events/__init__.py b/bot/exts/events/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/bot/exts/events/__init__.py
+++ /dev/null
diff --git a/bot/exts/events/code_jams/__init__.py b/bot/exts/events/code_jams/__init__.py
deleted file mode 100644
index 2f858d1f9..000000000
--- a/bot/exts/events/code_jams/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from bot.bot import Bot
-
-
-async def setup(bot: Bot) -> None:
- """Load the CodeJams cog."""
- from bot.exts.events.code_jams._cog import CodeJams
-
- await bot.add_cog(CodeJams(bot))
diff --git a/bot/exts/events/code_jams/_channels.py b/bot/exts/events/code_jams/_channels.py
deleted file mode 100644
index e8cf5f7bf..000000000
--- a/bot/exts/events/code_jams/_channels.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import typing as t
-
-import discord
-
-from bot.constants import Categories, Channels, Roles
-from bot.log import get_logger
-
-log = get_logger(__name__)
-
-MAX_CHANNELS = 50
-CATEGORY_NAME = "Code Jam"
-
-
-async def _get_category(guild: discord.Guild) -> discord.CategoryChannel:
- """
- Return a code jam category.
-
- If all categories are full or none exist, create a new category.
- """
- for category in guild.categories:
- if category.name == CATEGORY_NAME and len(category.channels) < MAX_CHANNELS:
- return category
-
- return await _create_category(guild)
-
-
-async def _create_category(guild: discord.Guild) -> discord.CategoryChannel:
- """Create a new code jam category and return it."""
- log.info("Creating a new code jam category.")
-
- category_overwrites = {
- guild.default_role: discord.PermissionOverwrite(read_messages=False),
- guild.me: discord.PermissionOverwrite(read_messages=True)
- }
-
- category = await guild.create_category_channel(
- CATEGORY_NAME,
- overwrites=category_overwrites,
- reason="It's code jam time!"
- )
-
- await _send_status_update(
- guild, f"Created a new category with the ID {category.id} for this Code Jam's team channels."
- )
-
- return category
-
-
-def _get_overwrites(
- members: list[tuple[discord.Member, bool]],
- guild: discord.Guild,
-) -> dict[t.Union[discord.Member, discord.Role], discord.PermissionOverwrite]:
- """Get code jam team channels permission overwrites."""
- team_channel_overwrites = {
- guild.default_role: discord.PermissionOverwrite(read_messages=False),
- guild.get_role(Roles.code_jam_event_team): discord.PermissionOverwrite(read_messages=True)
- }
-
- for member, _ in members:
- team_channel_overwrites[member] = discord.PermissionOverwrite(
- read_messages=True
- )
-
- return team_channel_overwrites
-
-
-async def create_team_channel(
- guild: discord.Guild,
- team_name: str,
- members: list[tuple[discord.Member, bool]],
- team_leaders: discord.Role
-) -> None:
- """Create the team's text channel."""
- await _add_team_leader_roles(members, team_leaders)
-
- # Get permission overwrites and category
- team_channel_overwrites = _get_overwrites(members, guild)
- code_jam_category = await _get_category(guild)
-
- # Create a text channel for the team
- await code_jam_category.create_text_channel(
- team_name,
- overwrites=team_channel_overwrites,
- )
-
-
-async def create_team_leader_channel(guild: discord.Guild, team_leaders: discord.Role) -> None:
- """Create the Team Leader Chat channel for the Code Jam team leaders."""
- category: discord.CategoryChannel = guild.get_channel(Categories.summer_code_jam)
-
- team_leaders_chat = await category.create_text_channel(
- name="team-leaders-chat",
- overwrites={
- guild.default_role: discord.PermissionOverwrite(read_messages=False),
- team_leaders: discord.PermissionOverwrite(read_messages=True)
- }
- )
-
- await _send_status_update(guild, f"Created {team_leaders_chat.mention} in the {category} category.")
-
-
-async def _send_status_update(guild: discord.Guild, message: str) -> None:
- """Inform the events lead with a status update when the command is ran."""
- channel: discord.TextChannel = guild.get_channel(Channels.code_jam_planning)
-
- await channel.send(f"<@&{Roles.events_lead}>\n\n{message}")
-
-
-async def _add_team_leader_roles(members: list[tuple[discord.Member, bool]], team_leaders: discord.Role) -> None:
- """Assign the team leader role to the team leaders."""
- for member, is_leader in members:
- if is_leader:
- await member.add_roles(team_leaders)
diff --git a/bot/exts/events/code_jams/_cog.py b/bot/exts/events/code_jams/_cog.py
deleted file mode 100644
index 86c357863..000000000
--- a/bot/exts/events/code_jams/_cog.py
+++ /dev/null
@@ -1,239 +0,0 @@
-import asyncio
-import csv
-import typing as t
-from collections import defaultdict
-
-import discord
-from discord import Colour, Embed, Guild, Member
-from discord.ext import commands
-
-from bot.bot import Bot
-from bot.constants import Emojis, Roles
-from bot.exts.events.code_jams import _channels
-from bot.log import get_logger
-from bot.utils.members import get_or_fetch_member
-from bot.utils.services import PasteTooLongError, PasteUploadError, send_to_paste_service
-
-log = get_logger(__name__)
-
-TEAM_LEADERS_COLOUR = 0x11806a
-DELETION_REACTION = "\U0001f4a5"
-
-
-class CodeJams(commands.Cog):
- """Manages the code-jam related parts of our server."""
-
- def __init__(self, bot: Bot):
- self.bot = bot
-
- @commands.group(aliases=("cj", "jam"))
- @commands.has_any_role(Roles.admins)
- async def codejam(self, ctx: commands.Context) -> None:
- """A Group of commands for managing Code Jams."""
- if ctx.invoked_subcommand is None:
- await ctx.send_help(ctx.command)
-
- @codejam.command()
- async def create(self, ctx: commands.Context, csv_file: t.Optional[str] = None) -> None:
- """
- Create code-jam teams from a CSV file or a link to one, specifying the team names, leaders and members.
-
- The CSV file must have 3 columns: 'Team Name', 'Team Member Discord ID', and 'Team Leader'.
-
- This will create the text channels for the teams, and give the team leaders their roles.
- """
- async with ctx.typing():
- if csv_file:
- async with self.bot.http_session.get(csv_file) as response:
- if response.status != 200:
- await ctx.send(f"Got a bad response from the URL: {response.status}")
- return
-
- csv_file = await response.text()
-
- elif ctx.message.attachments:
- csv_file = (await ctx.message.attachments[0].read()).decode("utf8")
- else:
- raise commands.BadArgument("You must include either a CSV file or a link to one.")
-
- teams = defaultdict(list)
- reader = csv.DictReader(csv_file.splitlines())
-
- for row in reader:
- member = await get_or_fetch_member(ctx.guild, int(row["Team Member Discord ID"]))
-
- if member is None:
- log.trace(f"Got an invalid member ID: {row['Team Member Discord ID']}")
- continue
-
- teams[row["Team Name"]].append((member, row["Team Leader"].upper() == "Y"))
-
- team_leaders = await ctx.guild.create_role(name="Code Jam Team Leaders", colour=TEAM_LEADERS_COLOUR)
-
- for team_name, team_members in teams.items():
- await _channels.create_team_channel(ctx.guild, team_name, team_members, team_leaders)
-
- await _channels.create_team_leader_channel(ctx.guild, team_leaders)
- await ctx.send(f"{Emojis.check_mark} Created Code Jam with {len(teams)} teams.")
-
- @codejam.command()
- @commands.has_any_role(Roles.admins)
- async def end(self, ctx: commands.Context) -> None:
- """
- Delete all code jam channels.
-
- A confirmation message is displayed with the categories and channels to be deleted.. Pressing the added reaction
- deletes those channels.
- """
- def predicate_deletion_emoji_reaction(reaction: discord.Reaction, user: discord.User) -> bool:
- """Return True if the reaction :boom: was added by the context message author on this message."""
- return (
- reaction.message.id == message.id
- and user.id == ctx.author.id
- and str(reaction) == DELETION_REACTION
- )
-
- # A copy of the list of channels is stored. This is to make sure that we delete precisely the channels displayed
- # in the confirmation message.
- categories = self.jam_categories(ctx.guild)
- category_channels = {category: category.channels.copy() for category in categories}
-
- confirmation_message = await self._build_confirmation_message(category_channels)
- message = await ctx.send(confirmation_message)
- await message.add_reaction(DELETION_REACTION)
- try:
- await self.bot.wait_for(
- 'reaction_add',
- check=predicate_deletion_emoji_reaction,
- timeout=10
- )
-
- except asyncio.TimeoutError:
- await message.clear_reaction(DELETION_REACTION)
- await ctx.send("Command timed out.", reference=message)
- return
-
- else:
- await message.clear_reaction(DELETION_REACTION)
- for category, channels in category_channels.items():
- for channel in channels:
- await channel.delete(reason="Code jam ended.")
- await category.delete(reason="Code jam ended.")
-
- await message.add_reaction(Emojis.check_mark)
-
- @staticmethod
- async def _build_confirmation_message(
- categories: dict[discord.CategoryChannel, list[discord.abc.GuildChannel]]
- ) -> str:
- """Sends details of the channels to be deleted to the pasting service, and formats the confirmation message."""
- def channel_repr(channel: discord.abc.GuildChannel) -> str:
- """Formats the channel name and ID and a readable format."""
- return f"{channel.name} ({channel.id})"
-
- def format_category_info(category: discord.CategoryChannel, channels: list[discord.abc.GuildChannel]) -> str:
- """Displays the category and the channels within it in a readable format."""
- return f"{channel_repr(category)}:\n" + "\n".join(" - " + channel_repr(channel) for channel in channels)
-
- deletion_details = "\n\n".join(
- format_category_info(category, channels) for category, channels in categories.items()
- )
-
- try:
- message = await send_to_paste_service(deletion_details)
- except PasteTooLongError:
- message = "**Too long to upload to paste service.**"
- except PasteUploadError:
- message = "**Failed to upload to paste service.**"
-
- return f"Are you sure you want to delete all code jam channels?\n\nThe channels to be deleted: {message}"
-
- @codejam.command()
- @commands.has_any_role(Roles.admins, Roles.code_jam_event_team)
- async def info(self, ctx: commands.Context, member: Member) -> None:
- """
- Send an info embed about the member with the team they're in.
-
- The team is found by searching the permissions of the team channels.
- """
- channel = self.team_channel(ctx.guild, member)
- if not channel:
- await ctx.send(":x: I can't find the team channel for this member.")
- return
-
- embed = Embed(
- title=str(member),
- colour=Colour.og_blurple()
- )
- embed.add_field(name="Team", value=self.team_name(channel), inline=True)
-
- await ctx.send(embed=embed)
-
- @codejam.command()
- @commands.has_any_role(Roles.admins)
- async def move(self, ctx: commands.Context, member: Member, new_team_name: str) -> None:
- """Move participant from one team to another by changing the user's permissions for the relevant channels."""
- old_team_channel = self.team_channel(ctx.guild, member)
- if not old_team_channel:
- await ctx.send(":x: I can't find the team channel for this member.")
- return
-
- if old_team_channel.name == new_team_name or self.team_name(old_team_channel) == new_team_name:
- await ctx.send(f"`{member}` is already in `{new_team_name}`.")
- return
-
- new_team_channel = self.team_channel(ctx.guild, new_team_name)
- if not new_team_channel:
- await ctx.send(f":x: I can't find a team channel named `{new_team_name}`.")
- return
-
- await old_team_channel.set_permissions(member, overwrite=None, reason=f"Participant moved to {new_team_name}")
- await new_team_channel.set_permissions(
- member,
- overwrite=discord.PermissionOverwrite(read_messages=True),
- reason=f"Participant moved from {old_team_channel.name}"
- )
-
- await ctx.send(
- f"Participant moved from `{self.team_name(old_team_channel)}` to `{self.team_name(new_team_channel)}`."
- )
-
- @codejam.command()
- @commands.has_any_role(Roles.admins)
- async def remove(self, ctx: commands.Context, member: Member) -> None:
- """Remove the participant from their team. Does not remove the participants or leader roles."""
- channel = self.team_channel(ctx.guild, member)
- if not channel:
- await ctx.send(":x: I can't find the team channel for this member.")
- return
-
- await channel.set_permissions(
- member,
- overwrite=None,
- reason=f"Participant removed from the team {self.team_name(channel)}."
- )
- await ctx.send(f"Removed the participant from `{self.team_name(channel)}`.")
-
- @staticmethod
- def jam_categories(guild: Guild) -> list[discord.CategoryChannel]:
- """Get all the code jam team categories."""
- return [category for category in guild.categories if category.name == _channels.CATEGORY_NAME]
-
- @staticmethod
- def team_channel(guild: Guild, criterion: t.Union[str, Member]) -> t.Optional[discord.TextChannel]:
- """Get a team channel through either a participant or the team name."""
- for category in CodeJams.jam_categories(guild):
- for channel in category.channels:
- if isinstance(channel, discord.TextChannel):
- if (
- # If it's a string.
- criterion == channel.name or criterion == CodeJams.team_name(channel)
- # If it's a member.
- or criterion in channel.overwrites
- ):
- return channel
-
- @staticmethod
- def team_name(channel: discord.TextChannel) -> str:
- """Retrieves the team name from the given channel."""
- return channel.name.replace("-", " ").title()
diff --git a/bot/rules/mentions.py b/bot/rules/mentions.py
deleted file mode 100644
index ca1d0c01c..000000000
--- a/bot/rules/mentions.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from typing import Dict, Iterable, List, Optional, Tuple
-
-from discord import DeletedReferencedMessage, Member, Message, MessageType, NotFound
-
-import bot
-from bot.log import get_logger
-
-log = get_logger(__name__)
-
-
-async def apply(
- last_message: Message, recent_messages: List[Message], config: Dict[str, int]
-) -> Optional[Tuple[str, Iterable[Member], Iterable[Message]]]:
- """
- Detects total mentions exceeding the limit sent by a single user.
-
- Excludes mentions that are bots, themselves, or replied users.
-
- In very rare cases, may not be able to determine a
- mention was to a reply, in which case it is not ignored.
- """
- relevant_messages = tuple(
- msg
- for msg in recent_messages
- if msg.author == last_message.author
- )
- # We use `msg.mentions` here as that is supplied by the api itself, to determine who was mentioned.
- # Additionally, `msg.mentions` includes the user replied to, even if the mention doesn't occur in the body.
- # In order to exclude users who are mentioned as a reply, we check if the msg has a reference
- #
- # While we could use regex to parse the message content, and get a list of
- # the mentions, that solution is very prone to breaking.
- # We would need to deal with codeblocks, escaping markdown, and any discrepancies between
- # our implementation and discord's markdown parser which would cause false positives or false negatives.
- total_recent_mentions = 0
- for msg in relevant_messages:
- # We check if the message is a reply, and if it is try to get the author
- # since we ignore mentions of a user that we're replying to
- reply_author = None
-
- if msg.type == MessageType.reply:
- ref = msg.reference
-
- if not (resolved := ref.resolved):
- # It is possible, in a very unusual situation, for a message to have a reference
- # that is both not in the cache, and deleted while running this function.
- # In such a situation, this will throw an error which we catch.
- try:
- resolved = await bot.instance.get_partial_messageable(resolved.channel_id).fetch_message(
- resolved.message_id
- )
- except NotFound:
- log.info('Could not fetch the reference message as it has been deleted.')
-
- if resolved and not isinstance(resolved, DeletedReferencedMessage):
- reply_author = resolved.author
-
- for user in msg.mentions:
- # Don't count bot or self mentions, or the user being replied to (if applicable)
- if user.bot or user in {msg.author, reply_author}:
- continue
- total_recent_mentions += 1
-
- if total_recent_mentions > config['max']:
- return (
- f"sent {total_recent_mentions} mentions in {config['interval']}s",
- (last_message.author,),
- relevant_messages
- )
- return None
diff --git a/tests/bot/exts/events/__init__.py b/tests/bot/exts/events/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/tests/bot/exts/events/__init__.py
+++ /dev/null
diff --git a/tests/bot/exts/events/test_code_jams.py b/tests/bot/exts/events/test_code_jams.py
deleted file mode 100644
index 684f7abcd..000000000
--- a/tests/bot/exts/events/test_code_jams.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import unittest
-from unittest.mock import AsyncMock, MagicMock, create_autospec, patch
-
-from discord import CategoryChannel
-from discord.ext.commands import BadArgument
-
-from bot.constants import Roles
-from bot.exts.events import code_jams
-from bot.exts.events.code_jams import _channels, _cog
-from tests.helpers import (
- MockAttachment, MockBot, MockCategoryChannel, MockContext, MockGuild, MockMember, MockRole, MockTextChannel,
- autospec
-)
-
-TEST_CSV = b"""\
-Team Name,Team Member Discord ID,Team Leader
-Annoyed Alligators,12345,Y
-Annoyed Alligators,54321,N
-Oscillating Otters,12358,Y
-Oscillating Otters,74832,N
-Oscillating Otters,19903,N
-Annoyed Alligators,11111,N
-"""
-
-
-def get_mock_category(channel_count: int, name: str) -> CategoryChannel:
- """Return a mocked code jam category."""
- category = create_autospec(CategoryChannel, spec_set=True, instance=True)
- category.name = name
- category.channels = [MockTextChannel() for _ in range(channel_count)]
-
- return category
-
-
-class JamCodejamCreateTests(unittest.IsolatedAsyncioTestCase):
- """Tests for `codejam create` command."""
-
- def setUp(self):
- self.bot = MockBot()
- self.admin_role = MockRole(name="Admins", id=Roles.admins)
- self.command_user = MockMember([self.admin_role])
- self.guild = MockGuild([self.admin_role])
- self.ctx = MockContext(bot=self.bot, author=self.command_user, guild=self.guild)
- self.cog = _cog.CodeJams(self.bot)
-
- async def test_message_without_attachments(self):
- """If no link or attachments are provided, commands.BadArgument should be raised."""
- self.ctx.message.attachments = []
-
- with self.assertRaises(BadArgument):
- await self.cog.create(self.cog, self.ctx, None)
-
- @patch.object(_channels, "create_team_channel")
- @patch.object(_channels, "create_team_leader_channel")
- async def test_result_sending(self, create_leader_channel, create_team_channel):
- """Should call `ctx.send` when everything goes right."""
- self.ctx.message.attachments = [MockAttachment()]
- self.ctx.message.attachments[0].read = AsyncMock()
- self.ctx.message.attachments[0].read.return_value = TEST_CSV
-
- team_leaders = MockRole()
-
- self.guild.get_member.return_value = MockMember()
-
- self.ctx.guild.create_role = AsyncMock()
- self.ctx.guild.create_role.return_value = team_leaders
- self.cog.add_roles = AsyncMock()
-
- await self.cog.create(self.cog, self.ctx, None)
-
- create_team_channel.assert_awaited()
- create_leader_channel.assert_awaited_once_with(
- self.ctx.guild, team_leaders
- )
- self.ctx.send.assert_awaited_once()
-
- async def test_link_returning_non_200_status(self):
- """When the URL passed returns a non 200 status, it should send a message informing them."""
- self.bot.http_session.get.return_value = mock = MagicMock()
- mock.status = 404
- await self.cog.create(self.cog, self.ctx, "https://not-a-real-link.com")
-
- self.ctx.send.assert_awaited_once()
-
- @patch.object(_channels, "_send_status_update")
- async def test_category_doesnt_exist(self, update):
- """Should create a new code jam category."""
- subtests = (
- [],
- [get_mock_category(_channels.MAX_CHANNELS, _channels.CATEGORY_NAME)],
- [get_mock_category(_channels.MAX_CHANNELS - 2, "other")],
- )
-
- for categories in subtests:
- update.reset_mock()
- self.guild.reset_mock()
- self.guild.categories = categories
-
- with self.subTest(categories=categories):
- actual_category = await _channels._get_category(self.guild)
-
- update.assert_called_once()
- self.guild.create_category_channel.assert_awaited_once()
- category_overwrites = self.guild.create_category_channel.call_args[1]["overwrites"]
-
- self.assertFalse(category_overwrites[self.guild.default_role].read_messages)
- self.assertTrue(category_overwrites[self.guild.me].read_messages)
- self.assertEqual(self.guild.create_category_channel.return_value, actual_category)
-
- async def test_category_channel_exist(self):
- """Should not try to create category channel."""
- expected_category = get_mock_category(_channels.MAX_CHANNELS - 2, _channels.CATEGORY_NAME)
- self.guild.categories = [
- get_mock_category(_channels.MAX_CHANNELS - 2, "other"),
- expected_category,
- get_mock_category(0, _channels.CATEGORY_NAME),
- ]
-
- actual_category = await _channels._get_category(self.guild)
- self.assertEqual(expected_category, actual_category)
-
- async def test_channel_overwrites(self):
- """Should have correct permission overwrites for users and roles."""
- leader = (MockMember(), True)
- members = [leader] + [(MockMember(), False) for _ in range(4)]
- overwrites = _channels._get_overwrites(members, self.guild)
-
- for member, _ in members:
- self.assertTrue(overwrites[member].read_messages)
-
- @patch.object(_channels, "_get_overwrites")
- @patch.object(_channels, "_get_category")
- @autospec(_channels, "_add_team_leader_roles", pass_mocks=False)
- async def test_team_channels_creation(self, get_category, get_overwrites):
- """Should create a text channel for a team."""
- team_leaders = MockRole()
- members = [(MockMember(), True)] + [(MockMember(), False) for _ in range(5)]
- category = MockCategoryChannel()
- category.create_text_channel = AsyncMock()
-
- get_category.return_value = category
- await _channels.create_team_channel(self.guild, "my-team", members, team_leaders)
-
- category.create_text_channel.assert_awaited_once_with(
- "my-team",
- overwrites=get_overwrites.return_value
- )
-
- async def test_jam_roles_adding(self):
- """Should add team leader role to leader and jam role to every team member."""
- leader_role = MockRole(name="Team Leader")
-
- leader = MockMember()
- members = [(leader, True)] + [(MockMember(), False) for _ in range(4)]
- await _channels._add_team_leader_roles(members, leader_role)
-
- leader.add_roles.assert_awaited_once_with(leader_role)
- for member, is_leader in members:
- if not is_leader:
- member.add_roles.assert_not_awaited()
-
-
-class CodeJamSetup(unittest.IsolatedAsyncioTestCase):
- """Test for `setup` function of `CodeJam` cog."""
-
- async def test_setup(self):
- """Should call `bot.add_cog`."""
- bot = MockBot()
- await code_jams.setup(bot)
- bot.add_cog.assert_awaited_once()