aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/constants.py6
-rw-r--r--bot/exts/utils/jams.py171
-rw-r--r--config-default.yml5
3 files changed, 111 insertions, 71 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 885b5c822..f33c14798 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -400,6 +400,8 @@ class Categories(metaclass=YAMLGetter):
modmail: int
voice: int
+ # 2021 Summer Code Jam
+ summer_code_jam: int
class Channels(metaclass=YAMLGetter):
section = "guild"
@@ -437,6 +439,7 @@ class Channels(metaclass=YAMLGetter):
discord_py: int
esoteric: int
voice_gate: int
+ code_jam_planning: int
admins: int
admin_spam: int
@@ -495,8 +498,10 @@ class Roles(metaclass=YAMLGetter):
admins: int
core_developers: int
+ code_jam_event_team: int
devops: int
domain_leads: int
+ events_lead: int
helpers: int
moderators: int
mod_team: int
@@ -504,7 +509,6 @@ class Roles(metaclass=YAMLGetter):
project_leads: int
jammers: int
- team_leaders: int
class Guild(metaclass=YAMLGetter):
diff --git a/bot/exts/utils/jams.py b/bot/exts/utils/jams.py
index 98fbcb303..d45f9b57f 100644
--- a/bot/exts/utils/jams.py
+++ b/bot/exts/utils/jams.py
@@ -1,17 +1,19 @@
+import csv
import logging
import typing as t
+from collections import defaultdict
-from discord import CategoryChannel, Guild, Member, PermissionOverwrite, Role
+import discord
from discord.ext import commands
-from more_itertools import unique_everseen
from bot.bot import Bot
-from bot.constants import Roles
+from bot.constants import Categories, Channels, Emojis, Roles
log = logging.getLogger(__name__)
MAX_CHANNELS = 50
CATEGORY_NAME = "Code Jam"
+TEAM_LEADERS_COLOUR = 0x11806a
class CodeJams(commands.Cog):
@@ -20,39 +22,57 @@ class CodeJams(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
- @commands.command()
+ @commands.group()
@commands.has_any_role(Roles.admins)
- async def createteam(self, ctx: commands.Context, team_name: str, members: commands.Greedy[Member]) -> None:
+ 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:
"""
- Create team channels (voice and text) in the Code Jams category, assign roles, and add overwrites for the team.
+ 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'.
- The first user passed will always be the team leader.
+ This will create the text channels for the teams, and give the team leaders their roles.
"""
- # Ignore duplicate members
- members = list(unique_everseen(members))
-
- # We had a little issue during Code Jam 4 here, the greedy converter did it's job
- # and ignored anything which wasn't a valid argument which left us with teams of
- # two members or at some times even 1 member. This fixes that by checking that there
- # are always 3 members in the members list.
- if len(members) < 3:
- await ctx.send(
- ":no_entry_sign: One of your arguments was invalid\n"
- f"There must be a minimum of 3 valid members in your team. Found: {len(members)}"
- " members"
- )
- return
+ 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
- team_channel = await self.create_channels(ctx.guild, team_name, members)
- await self.add_roles(ctx.guild, members)
+ csv_file = await response.text()
- await ctx.send(
- f":ok_hand: Team created: {team_channel}\n"
- f"**Team Leader:** {members[0].mention}\n"
- f"**Team Members:** {' '.join(member.mention for member in members[1:])}"
- )
+ 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 = ctx.guild.get_member(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="Team Leaders", colour=TEAM_LEADERS_COLOUR)
+
+ for team_name, members in teams.items():
+ await self.create_team_channel(ctx.guild, team_name, members, team_leaders)
- async def get_category(self, guild: Guild) -> CategoryChannel:
+ await self.create_team_leader_channel(ctx.guild, team_leaders)
+ await ctx.send(f"{Emojis.check_mark} Created Code Jam with {len(teams)} teams.")
+
+ async def get_category(self, guild: discord.Guild) -> discord.CategoryChannel:
"""
Return a code jam category.
@@ -60,84 +80,97 @@ class CodeJams(commands.Cog):
"""
for category in guild.categories:
# Need 2 available spaces: one for the text channel and one for voice.
- if category.name == CATEGORY_NAME and MAX_CHANNELS - len(category.channels) >= 2:
+ if category.name == CATEGORY_NAME and len(category.channels) < MAX_CHANNELS:
return category
return await self.create_category(guild)
- @staticmethod
- async def create_category(guild: Guild) -> CategoryChannel:
+ async def create_category(self, 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: PermissionOverwrite(read_messages=False),
- guild.me: PermissionOverwrite(read_messages=True)
+ guild.default_role: discord.PermissionOverwrite(read_messages=False),
+ guild.me: discord.PermissionOverwrite(read_messages=True)
}
- return await guild.create_category_channel(
+ category = await guild.create_category_channel(
CATEGORY_NAME,
overwrites=category_overwrites,
reason="It's code jam time!"
)
+ await self.send_status_update(
+ guild, f"Created a new category with the ID {category.id} for this Code Jam's team channels."
+ )
+
+ return category
+
@staticmethod
- def get_overwrites(members: t.List[Member], guild: Guild) -> t.Dict[t.Union[Member, Role], PermissionOverwrite]:
+ 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."""
- # First member is always the team leader
team_channel_overwrites = {
- members[0]: PermissionOverwrite(
- manage_messages=True,
- read_messages=True,
- manage_webhooks=True,
- connect=True
- ),
- guild.default_role: PermissionOverwrite(read_messages=False, connect=False),
+ guild.default_role: discord.PermissionOverwrite(read_messages=False),
+ guild.get_role(Roles.moderators): discord.PermissionOverwrite(read_messages=True),
+ guild.get_role(Roles.code_jam_event_team): discord.PermissionOverwrite(read_messages=True)
}
- # Rest of members should just have read_messages
- for member in members[1:]:
- team_channel_overwrites[member] = PermissionOverwrite(
- read_messages=True,
- connect=True
+ for member, _ in members:
+ team_channel_overwrites[member] = discord.PermissionOverwrite(
+ read_messages=True
)
return team_channel_overwrites
- async def create_channels(self, guild: Guild, team_name: str, members: t.List[Member]) -> str:
- """Create team text and voice channels. Return the mention for the text channel."""
+ async def create_team_channel(
+ self,
+ guild: discord.Guild,
+ team_name: str,
+ members: list[tuple[discord.Member, bool]],
+ team_leaders: discord.Role
+ ) -> None:
+ """Create the team's text channel."""
+ await self.add_team_leader_roles(members, team_leaders)
+
# Get permission overwrites and category
team_channel_overwrites = self.get_overwrites(members, guild)
code_jam_category = await self.get_category(guild)
# Create a text channel for the team
- team_channel = await guild.create_text_channel(
+ await code_jam_category.create_text_channel(
team_name,
overwrites=team_channel_overwrites,
- category=code_jam_category
)
- # Create a voice channel for the team
- team_voice_name = " ".join(team_name.split("-")).title()
+ async def create_team_leader_channel(self, 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)
- await guild.create_voice_channel(
- team_voice_name,
- overwrites=team_channel_overwrites,
- category=code_jam_category
+ 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)
+ }
)
- return team_channel.mention
+ await self.send_status_update(guild, f"Created {team_leaders_chat.mention} in the {category} category.")
+
+ async def send_status_update(self, 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}")
@staticmethod
- async def add_roles(guild: Guild, members: t.List[Member]) -> None:
- """Assign team leader and jammer roles."""
- # Assign team leader role
- await members[0].add_roles(guild.get_role(Roles.team_leaders))
-
- # Assign rest of roles
- jammer_role = guild.get_role(Roles.jammers)
- for member in members:
- await member.add_roles(jammer_role)
+ async def add_team_leader_roles(members: list[tuple[discord.Member, bool]], team_leaders: discord.Role) -> None:
+ """Assign team leader role, the jammer role and their team role."""
+ for member, is_leader in members:
+ if is_leader:
+ await member.add_roles(team_leaders)
def setup(bot: Bot) -> None:
diff --git a/config-default.yml b/config-default.yml
index 394c51c26..8c30ecf69 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -142,6 +142,7 @@ guild:
moderators: &MODS_CATEGORY 749736277464842262
modmail: &MODMAIL 714494672835444826
voice: 356013253765234688
+ summer_code_jam: 861692638540857384
channels:
# Public announcement and news channels
@@ -185,6 +186,7 @@ guild:
bot_commands: &BOT_CMD 267659945086812160
esoteric: 470884583684964352
voice_gate: 764802555427029012
+ code_jam_planning: 490217981872177157
# Staff
admins: &ADMINS 365960823622991872
@@ -258,8 +260,10 @@ guild:
# Staff
admins: &ADMINS_ROLE 267628507062992896
core_developers: 587606783669829632
+ code_jam_event_team: 787816728474288181
devops: 409416496733880320
domain_leads: 807415650778742785
+ events_lead: 778361735739998228
helpers: &HELPERS_ROLE 267630620367257601
moderators: &MODS_ROLE 831776746206265384
mod_team: &MOD_TEAM_ROLE 267629731250176001
@@ -268,7 +272,6 @@ guild:
# Code Jam
jammers: 737249140966162473
- team_leaders: 737250302834638889
# Streaming
video: 764245844798079016