aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ToxicKidz <[email protected]>2021-07-05 17:26:11 -0400
committerGravatar ToxicKidz <[email protected]>2021-07-05 17:26:11 -0400
commit7d1ee897b565daef1a8cc073d4dbaf0602185528 (patch)
treec3e429a98b64e4724c5ce27b146a09632e4c8642
parentMerge pull request #1593 from python-discord/flake-8-isn't-a-task (diff)
chore: Add the codejam create command
This command takes a CSV file or a link to one. This CSV file has three rows: 'Team Name', 'Team Member Discord ID', and 'Team Leader'. The Team Name will be the name of the team's channel, the Member ID tells which user belongs to this team, and leam leader, which is either Y/N, tells if this user is the team leader. It will create text channels for each team and make a team leaders chat channel as well. It will ping the Events Lead role with updates for this command.
-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