-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add anonymous report feature * Add Away feature for leaders * Convert invite command to view * Fix Dr. Schwartz email, add transparency statement * Away view should be persistent * Reports changes for Summer 2024 (and beyond): * Add 'Report History' button for reviewing report history * Reports are now graded on red-yellow-green scale * Add report reviewing for leaders each week * Update report instructions embed w/ more info * Members are emailed upon report being reviewed * Fix report period * Use single quote instead of multi quote
- Loading branch information
Showing
7 changed files
with
1,004 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Literal | ||
|
||
import discord | ||
|
||
from .email import send_email | ||
from .views import MILBotModal, MILBotView | ||
|
||
if TYPE_CHECKING: | ||
from .bot import MILBot | ||
|
||
|
||
IntendedTargets = Literal["schwartz", "operations", "leaders"] | ||
|
||
|
||
class AnonymousReportModal(MILBotModal): | ||
|
||
report = discord.ui.TextInput( | ||
label="Report", | ||
placeholder="Enter your report here", | ||
style=discord.TextStyle.long, | ||
max_length=2000, | ||
) | ||
|
||
def __init__(self, bot: MILBot, target: IntendedTargets): | ||
self.bot = bot | ||
self.target = target | ||
super().__init__(title="Submit an Anonymous Report") | ||
|
||
async def on_submit(self, interaction: discord.Interaction): | ||
embed = discord.Embed( | ||
title="New Anonymous Report", | ||
color=discord.Color.light_gray(), | ||
description=self.report.value, | ||
) | ||
embed.set_footer(text="Submitted by an anonymous user") | ||
if self.target == "schwartz": | ||
html = f"""A new anonymous report has been submitted. The report is as follows: | ||
<blockquote>{self.report.value}</blockquote> | ||
Replies to this email will not be received. Please address any concerns with the appropriate leadership team.""" | ||
text = f"""A new anonymous report has been submitted. The report is as follows: | ||
{self.report.value} | ||
Replies to this email will not be received. Please address any concerns with the appropriate leadership team.""" | ||
await send_email( | ||
"[email protected]", | ||
"New Anonymous Report Received", | ||
html, | ||
text, | ||
) | ||
elif self.target == "operations": | ||
await self.bot.operations_leaders_channel.send(embed=embed) | ||
elif self.target == "leaders": | ||
await self.bot.leaders_channel.send(embed=embed) | ||
await interaction.response.send_message( | ||
"Your report has been submitted. Your input is invaluable, and we are committed to making MIL a better place for everyone. Thank you for helping us improve.", | ||
embed=embed, | ||
ephemeral=True, | ||
) | ||
|
||
|
||
class AnonymousTargetSelect(discord.ui.Select): | ||
def __init__(self, bot: MILBot): | ||
self.bot = bot | ||
options = [ | ||
discord.SelectOption(label="Dr. Schwartz", emoji="👨🏫", value="schwartz"), | ||
discord.SelectOption( | ||
label="Operations Leadership", | ||
emoji="👷", | ||
value="operations", | ||
), | ||
discord.SelectOption(label="Leaders", emoji="👑", value="leaders"), | ||
] | ||
super().__init__( | ||
placeholder="Select the target of your report", | ||
options=options, | ||
) | ||
|
||
async def callback(self, interaction: discord.Interaction): | ||
target = self.values[0] | ||
await interaction.response.send_modal(AnonymousReportModal(self.bot, target)) # type: ignore | ||
|
||
|
||
class AnonymousReportView(MILBotView): | ||
def __init__(self, bot: MILBot): | ||
self.bot = bot | ||
super().__init__(timeout=None) | ||
|
||
@discord.ui.button( | ||
label="Submit an anonymous report", | ||
style=discord.ButtonStyle.red, | ||
custom_id="anonymous_report:submit", | ||
) | ||
async def submit_anonymous_report( | ||
self, | ||
interaction: discord.Interaction, | ||
button: discord.ui.Button, | ||
): | ||
view = MILBotView() | ||
view.add_item(AnonymousTargetSelect(self.bot)) | ||
await interaction.response.send_message( | ||
"Select the target of your report.", | ||
view=view, | ||
ephemeral=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,26 +7,39 @@ | |
SEMESTERS = [ | ||
(datetime.date(2023, 8, 23), datetime.date(2023, 12, 6)), | ||
(datetime.date(2024, 1, 8), datetime.date(2024, 4, 28)), | ||
(datetime.date(2024, 5, 13), datetime.date(2024, 8, 9)), | ||
(datetime.date(2024, 5, 20), datetime.date(2024, 7, 29)), | ||
] | ||
SCHWARTZ_EMAIL = "[email protected]" | ||
|
||
|
||
def semester_given_date( | ||
date: datetime.datetime, | ||
*, | ||
next_semester: bool = False, | ||
) -> tuple[datetime.date, datetime.date] | None: | ||
for semester in SEMESTERS: | ||
if semester[0] <= date.date() <= semester[1]: | ||
return semester | ||
if next_semester and date.date() < semester[0]: | ||
return semester | ||
return None | ||
|
||
|
||
class Team(enum.Enum): | ||
SOFTWARE = auto() | ||
ELECTRICAL = auto() | ||
MECHANICAL = auto() | ||
SYSTEMS = auto() | ||
GENERAL = auto() | ||
|
||
@classmethod | ||
def from_str(cls, ss_str: str) -> Team: | ||
if "software" in ss_str.lower(): | ||
if "software" in ss_str.lower() or "S" in ss_str: | ||
return cls.SOFTWARE | ||
if "electrical" in ss_str.lower(): | ||
elif "electrical" in ss_str.lower() or "E" in ss_str: | ||
return cls.ELECTRICAL | ||
if "mechanical" in ss_str.lower(): | ||
elif "mechanical" in ss_str.lower() or "M" in ss_str: | ||
return cls.MECHANICAL | ||
return cls.SYSTEMS | ||
return cls.GENERAL | ||
|
||
@property | ||
def emoji(self) -> str: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,74 @@ | ||
from dataclasses import dataclass | ||
from email.mime.multipart import MIMEMultipart | ||
from email.mime.text import MIMEText | ||
from enum import Enum | ||
|
||
import aiosmtplib | ||
|
||
from .env import EMAIL_PASSWORD, EMAIL_USERNAME | ||
|
||
|
||
class HeaderPrefix(Enum): | ||
FULL = LONG = "[Machine Intelligence Laboratory]" | ||
INITIALS = SHORT = "[MIL]" | ||
|
||
|
||
@dataclass | ||
class Email: | ||
receiver_emails: list[str] | ||
subject: str | ||
html: str | ||
text: str | None = None | ||
cc_emails: list[str] | None = None | ||
header_prefix: HeaderPrefix | None = HeaderPrefix.INITIALS | ||
|
||
async def send(self) -> None: | ||
port = 587 | ||
hostname = "smtp.ufl.edu" | ||
sender_email = EMAIL_USERNAME | ||
password = EMAIL_PASSWORD | ||
if not sender_email or not password: | ||
raise RuntimeError( | ||
"No email username and/or password found! Cannot send email.", | ||
) | ||
|
||
smtp_server = aiosmtplib.SMTP() | ||
await smtp_server.connect(hostname=hostname, port=port) | ||
await smtp_server.login(sender_email, password) | ||
custom_email = "[email protected]" | ||
|
||
# Create a multipart message and set headers | ||
message = MIMEMultipart("alternative") | ||
message["From"] = custom_email | ||
message["To"] = ", ".join(self.receiver_emails) | ||
message["Subject"] = ( | ||
f"{self.header_prefix.value} {self.subject}" | ||
if self.header_prefix | ||
else self.subject | ||
) | ||
|
||
# Turn these into plain/html MIMEText objects | ||
if self.text is None: | ||
# Attempt to convert HTML to text | ||
self.text = ( | ||
self.html.replace("<p>", " ").replace("</p>", " ").replace("\n", " ") | ||
) | ||
|
||
text_footer = " --- This is an automated message. Replies will not be received." | ||
html_footer = "\n\n<p>---<br>This is an automated message. Replies will not be received.</p>" | ||
part1 = MIMEText(self.text + text_footer, "plain") | ||
part2 = MIMEText(self.html + html_footer, "html") | ||
message.attach(part1) | ||
message.attach(part2) | ||
|
||
await smtp_server.sendmail( | ||
custom_email, | ||
self.receiver_emails, | ||
message.as_string(), | ||
) | ||
await smtp_server.quit() | ||
|
||
|
||
async def send_email(receiver_email, subject, html, text) -> bool: | ||
port = 587 | ||
hostname = "smtp.ufl.edu" | ||
|
Oops, something went wrong.