Skip to content

Commit

Permalink
Reports changes for Summer 2024 (and beyond):
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cbrxyz committed May 20, 2024
1 parent fa0e7c7 commit f5af490
Show file tree
Hide file tree
Showing 4 changed files with 667 additions and 91 deletions.
14 changes: 13 additions & 1 deletion src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .github import GitHub, GitHubInviteView
from .leaders import AwayView
from .projects import SoftwareProjectsView
from .reports import ReportsCog, ReportsView
from .reports import ReportsCog, ReportsView, StartReviewView
from .roles import MechanicalRolesView, SummerRolesView, TeamRolesView
from .tasks import TaskManager
from .testing import TestingSignUpView
Expand Down Expand Up @@ -234,6 +234,7 @@ async def setup_hook(self) -> None:
self.add_view(AnonymousReportView(self))
self.add_view(GitHubInviteView(self))
self.add_view(AwayView(self))
self.add_view(StartReviewView(self))

agcm = gspread_asyncio.AsyncioGspreadClientManager(get_creds)
self.agc = await agcm.authorize()
Expand Down Expand Up @@ -386,6 +387,17 @@ async def reading_gif(self) -> discord.File:
raise ResourceNotFound("Cat gif not found.")
return discord.File(BytesIO(await resp.read()), filename="cat.gif")

async def good_job_gif(self) -> discord.File:
gifs = [
"https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExb3BqbnlzaXBmODdxMzRkeHFxYWg1N3NoM3A4czh3aGo2NHhmNGRtYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ktfInKGOVkdQtwJy9h/giphy.gif",
"https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExdjRyOGJjY3cxdTUzb2gycXlmcW1lZ2ZsYXh3aGxjaDY1cTNyMzRnNiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/UjCXeFnYcI2R2/giphy.gif",
"https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXo1dHdxem04N2M4ZXJhaTlnb25mYTZsMmtjMGFyMWJweDFleG03ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/8pR7lPuRZzTXVtWlTF/giphy.gif",
]
async with self.session.get(random.choice(gifs)) as resp:
if resp.status != 200:
raise ResourceNotFound("Cat gif not found.")
return discord.File(BytesIO(await resp.read()), filename="cat.gif")

async def on_message(self, message):
# don't respond to ourselves
if message.author == self.user:
Expand Down
25 changes: 19 additions & 6 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, 6), datetime.date(2024, 6, 9)),
]
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:
Expand Down
63 changes: 63 additions & 0 deletions src/email.py
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"
Expand Down
Loading

0 comments on commit f5af490

Please sign in to comment.