Files
contest-assistant/bot/cogs/contest.py

210 lines
8.7 KiB
Python

import logging
import discord
from discord.ext import commands
from discord.ext.commands import Context
from bot import checks, constants
from bot.bot import ContestBot
from bot.models import Guild, Period, PeriodStates, Submission
logger = logging.getLogger(__file__)
logger.setLevel(constants.LOGGING_LEVEL)
expected_deletions = []
class ContestCog(commands.Cog):
def __init__(self, bot: ContestBot):
self.bot = bot
@commands.command()
@commands.guild_only()
@checks.privileged()
async def prefix(self, ctx, new_prefix: str):
"""Changes the bot's saved prefix."""
with self.bot.get_session() as session:
guild = session.query(Guild).filter_by(id=ctx.guild.id).first()
if 1 <= len(new_prefix) <= 2:
if guild.prefix == new_prefix:
return await ctx.send(f':no_entry_sign: The prefix is already `{new_prefix}`.')
else:
guild.prefix = new_prefix
return await ctx.send(f':white_check_mark: Prefix changed to `{new_prefix}`.')
else:
return await ctx.send(':no_entry_sign: Invalid argument. Prefix must be 1 or 2 characters long.')
@commands.command()
@commands.guild_only()
@checks.privileged()
async def submission(self, ctx: Context, new_submission: discord.TextChannel) -> None:
"""Changes the bot's saved submission channel."""
with self.bot.get_session() as session:
guild = session.query(Guild).filter_by(id=ctx.guild.id).first()
if guild.submission is not None and guild.submission == new_submission.id:
await ctx.send(f':no_entry_sign: The submission channel is already set to {new_submission.mention}.')
else:
guild.submission_channel = new_submission
await ctx.send(f':white_check_mark: Submission channel changed to {new_submission.mention}.')
@commands.command()
@commands.guild_only()
@checks.privileged()
async def advance(self, ctx: Context, duration: float = None, pingback: bool = True) -> None:
"""
Advance the state of the current period pertaining to this Guild.
:param ctx:
:param duration: If given,
:param pingback: Whether or not the user should be pinged back when the duration is passed.
"""
assert duration == -1 or duration >= 0, "Duration must"
with self.bot.get_session() as session:
guild: Guild = session.query(Guild).get(ctx.guild.id)
period: Period = guild.current_period
# Handle non-existent or previously completed period in the current guild
if period is None or not period.active:
session.add(Period(id=ctx.guild.id))
# Handle previous period being completed.
elif period.state == PeriodStates.READY:
# TODO: Open the channel to messages
pass
# Handle submissions state
elif period.state == PeriodStates.SUBMISSIONS:
# TODO: Close the channel to messages
return
# Handle voting state
elif period.state == PeriodStates.PAUSED:
# TODO: Add all reactions to every submission
# TODO: Unlock channel reactions
# TODO: Close channel submissions
return
# Print period submissions
elif period.state == PeriodStates.VOTING:
# TODO: Fetch all submissions related to this period
# TODO: Create new period for Guild at
return
@commands.command()
@commands.guild_only()
@checks.privileged()
async def voting(self, ctx: Context, duration: float = None) -> None:
"""Closes submissions and sets up the voting period."""
if duration < 0:
await ctx.send('Invalid duration - must be non-negative.')
@commands.command()
@commands.guild_only()
@checks.privileged()
async def close(self, ctx: Context) -> None:
"""Closes the voting period."""
pass
@commands.command()
@commands.guild_only()
async def status(self, ctx: Context) -> None:
"""Provides the bot's current state in relation to internal configuration and the server's contest, if active."""
pass
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
if message.author == self.bot.user or message.author.bot or not message.guild: return
with self.bot.get_session() as session:
guild = session.query(Guild).get(message.guild.id)
print(session.query(Guild).all)
channel: discord.TextChannel = message.channel
if channel.id == guild.submission:
attachments = message.attachments
# TODO: Do attachment filtering between videos/files/audio etc.
# Ensure that the submission contains at least one attachment
if len(attachments) == 0:
await message.delete(delay=1)
warning = await channel.send(
f':no_entry_sign: {message.author.mention} Each submission must contain exactly one image.')
await warning.delete(delay=5)
# Ensure the image contains no more than one attachment
elif len(attachments) > 1:
await message.delete(delay=1)
warning = await channel.send(
f':no_entry_sign: {message.author.mention} Each submission must contain exactly one image.')
await warning.delete(delay=5)
else:
last_submission = session.query(Submission).filter_by(id=message.guild.id, user=message.author.id)
if last_submission is not None:
# delete last submission
submission_msg = await channel.fetch_message(last_submission)
if submission_msg is None:
logger.error(f'Unexpected: submission message {last_submission} could not be found.')
else:
await submission_msg.delete()
logger.info(f'Old submission deleted. {last_submission} (Old) -> {message.id} (New)')
# Delete the old submission row
session.delete(last_submission)
# Add the new submission row
session.add(
Submission(id=message.id, user=message.author.id, period=guild.current_period, timestamp=message.created_at))
logger.info(f'New submission created ({message.id}).')
@commands.Cog.listener()
async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent) -> None:
"""Handles submission deletions by the users, moderators or other bots for any reason."""
await self.bot.wait_until_ready()
# Ignore messages we delete
if payload.message_id in expected_deletions:
expected_deletions.remove(payload.message_id)
return
with self.bot.get_session() as session:
guild: Guild = session.query(Guild).get(payload.guild_id)
# If the message was cached, check that it's in the correct channel.
if payload.cached_message is not None and payload.cached_message.channel.id != guild.submission_channel:
return
submission = session.query(Submission).get(payload.message_id)
if submission is None:
logger.error(f'Submission {payload.message_id} could not be deleted from database as it was not found.')
else:
author: str = payload.cached_message.author.display_name if payload.cached_message is not None else 'Unknown'
logger.info(f'Submission {payload.message_id} by {author} deleted by outside source.')
session.delete(submission)
@commands.Cog.listener()
async def on_raw_bulk_message_delete(self, payload: discord.RawBulkMessageDeleteEvent) -> None:
pass
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
pass
@commands.Cog.listener()
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent) -> None:
pass
@commands.Cog.listener()
async def on_raw_reaction_clear(self, payload: discord.RawReactionActionEvent) -> None:
pass
@commands.Cog.listener()
async def on_raw_reaction_clear_emoji(self, payload: discord.RawReactionClearEmojiEvent) -> None:
pass
def setup(bot) -> None:
bot.add_cog(ContestCog(bot))