Skip to content

Commit

Permalink
Merge pull request #129 from royh02/master
Browse files Browse the repository at this point in the history
Discord Event Announcements Finished
  • Loading branch information
royh02 authored Jun 4, 2021
2 parents e185d56 + b725aa8 commit 2e2f937
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@ data/

# Virtual environment
/venv
.vscode/settings.json
3 changes: 2 additions & 1 deletion apps/csua_backend/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""
import os

from apps.discordbot.bot import csua_bot

# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
Expand All @@ -34,7 +36,6 @@

# We start discordbot thread here so that it doesn't interfere with other django
# commands such as test and migrate
from apps.discordbot.bot import csua_bot

if csua_bot:
csua_bot.thread.start()
29 changes: 29 additions & 0 deletions apps/db_data/0023_datetime_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime

from django.db import migrations, models
from django.db.migrations.operations.special import RunPython


def date_to_datetime(apps, schema_editor):
Event = apps.get_model("db_data", "Event")
for event in Event.objects.all():
d = event.date
event.start_time = datetime(year=d.year, month=d.month, day=d.day)
event.save()


class Migration(migrations.Migration):

dependencies = [("db_data", "0020_auto_20200728_2346")]

operations = [
migrations.AddField(
model_name="event", name="start_time", field=models.DateTimeField(null=True)
),
migrations.AddField(
model_name="event", name="end_time", field=models.DateTimeField(null=True)
),
migrations.RunPython(date_to_datetime),
migrations.RemoveField(model_name="event", name="date"),
migrations.RemoveField(model_name="event", name="time"),
]
28 changes: 28 additions & 0 deletions apps/db_data/migrations/0021_auto_20210515_1807.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from datetime import datetime

from django.db import migrations, models


def date_to_datetime(apps, schema_editor):
Event = apps.get_model("db_data", "Event")
for event in Event.objects.all():
d = event.date
event.start_time = datetime(year=d.year, month=d.month, day=d.day)
event.save()


class Migration(migrations.Migration):

dependencies = [("db_data", "0020_auto_20200728_2346")]

operations = [
migrations.AddField(
model_name="event", name="start_time", field=models.DateTimeField(null=True)
),
migrations.AddField(
model_name="event", name="end_time", field=models.DateTimeField(null=True)
),
migrations.RunPython(date_to_datetime),
migrations.RemoveField(model_name="event", name="date"),
migrations.RemoveField(model_name="event", name="time"),
]
26 changes: 20 additions & 6 deletions apps/db_data/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.contrib.auth.models import User as DjangoUser
from django.db import models
from django.utils import timezone


class Semester(models.Model):
Expand Down Expand Up @@ -168,8 +169,8 @@ class Sponsorship(models.Model):
class Event(models.Model):
name = models.CharField(max_length=70)
location = models.CharField(max_length=70)
date = models.DateField(null=True)
time = models.CharField(max_length=70)
start_time = models.DateTimeField(null=True)
end_time = models.DateTimeField(null=True)
description = models.TextField()
link = models.URLField(blank=True)
category = models.ForeignKey(
Expand All @@ -179,13 +180,26 @@ class Event(models.Model):
on_delete=models.PROTECT,
help_text="Currently unused.",
)

def __str__(self):
return f"{self.name} ({self.date})"
ordering = ["start_time"]

@property
def is_passed(self):
return self.date < datetime.date.today()
return self.start_time < timezone.now()

def get_start_date_and_time_string(self):
return self.start_time.astimezone(timezone.get_current_timezone()).strftime(
"%x %I:%M %p %Z"
)

def get_end_date_and_time_string(self):
return self.end_time.astimezone(timezone.get_current_timezone()).strftime(
"%x %I:%M %p %Z"
)

def __str__(self):
if not self.start_time:
return f"{self.name}"
return f"{self.name} ({self.get_start_date_and_time_string()})"


class EventCategory(models.Model):
Expand Down
2 changes: 1 addition & 1 deletion apps/db_data/templatetags/db_data_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@register.simple_tag
def get_upcoming_events():
return Event.objects.filter(date__gte=datetime.date.today())
return Event.objects.filter(start_time__gte=datetime.datetime.now())


@register.simple_tag
Expand Down
62 changes: 62 additions & 0 deletions apps/discordbot/annoucements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import datetime
from enum import Enum

from django.utils import timezone

from apps.db_data.models import Event


class AnnouncementType(Enum):
WEEK = "week"
TOMORROW = "tomorrow"
TODAY = "today"
HOUR = "hour"
B_TIME = "now" # Berkeley Time


def get_events_in_time_delta(requested_atype: AnnouncementType):
"""
Retrieves a list of Event objects within the requested
time delta.
requested_atype will take in enum constants in
AnnouncementType
"""
now = timezone.now().astimezone(timezone.get_current_timezone())
events = get_events_in_time_range(*timeify(requested_atype))
return events


def get_events_in_time_range(start_time, end_time):
"""
Takes in a two datetime objects, start_time and end_time
Returns events within the datetime range
"""
events = Event.objects.filter(start_time__gte=start_time).filter(
start_time__lte=end_time
)
return events


def timeify(requested_atype: AnnouncementType):
"""
Converts requested_tdelta string into a corresponding datetime object
"""
now = timezone.now()
time_ranges = {
AnnouncementType.WEEK: (now, now + datetime.timedelta(weeks=1)),
AnnouncementType.TOMORROW: (
now + datetime.timedelta(days=1),
now + datetime.timedelta(days=1, hours=23, minutes=59, seconds=59),
),
AnnouncementType.TODAY: (
now,
now + datetime.timedelta(hours=23, minutes=59, seconds=59),
),
AnnouncementType.HOUR: (
now + datetime.timedelta(hours=1),
now + datetime.timedelta(hours=1, minutes=59, seconds=59),
),
AnnouncementType.B_TIME: (now, now + datetime.timedelta(minutes=10)),
}
return time_ranges[requested_atype]
81 changes: 80 additions & 1 deletion apps/discordbot/bot.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import asyncio
import logging
import threading
import time
import unicodedata
from functools import partial

import discord
import schedule
from decouple import config
from discord.embeds import Embed
from discord.utils import get
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from pyfiglet import figlet_format

from . import connect4, cowsay, xkcd
from .annoucements import AnnouncementType, get_events_in_time_delta
from .utils import send_verify_mail

intents = discord.Intents.all()
Expand All @@ -21,6 +27,10 @@
CSUA_PHILBOT_CLIENT_ID = config("BOT_ID", default=737930184837300274, cast=int)
HOSER_ROLE_ID = config("TEST_ROLE", default=785418569412116513, cast=int) # Verified
DEBUG_CHANNEL_ID = config("DEBUG_CHANNEL", default=788989977794707456, cast=int)
ANNOUNCEMENTS_CHANNEL_ID = config(
"ANNOUNCEMENTS_CHANNEL", default=784902200102354989, cast=int
) # set to chatter for testing
ROY_TEST_SERVER_CHANNEL_ID = 805590450136154125
TIMEOUT_SECS = 10

logger = logging.getLogger(__name__)
Expand All @@ -30,6 +40,10 @@ class CSUAClient(discord.Client):
async def on_ready(self):
print(f"{self.user} has connected to Discord")
self.is_phillip = self.user.id == CSUA_PHILBOT_CLIENT_ID
csua_bot.announcements_thread = threading.Thread(
target=csua_bot.event_announcement, daemon=True
)
csua_bot.announcements_thread.start()
if self.is_phillip:
self.csua_guild = get(self.guilds, id=CSUA_GUILD_ID)
self.test_channel = get(self.csua_guild.channels, id=DEBUG_CHANNEL_ID)
Expand Down Expand Up @@ -166,7 +180,6 @@ def __init__(self):
def _start(self):
asyncio.set_event_loop(self.loop)
self.client = CSUAClient(intents=intents)

try:
self.loop.run_until_complete(self.client.start(TOKEN))
finally:
Expand All @@ -188,6 +201,72 @@ def promote_user_to_hoser(self, tag):
return True
return False

def event_announcement(self):
print("Announcements Thread started...")

times_msg = {
AnnouncementType.WEEK: "NEXT WEEK",
AnnouncementType.TODAY: "TODAY",
AnnouncementType.TOMORROW: "TOMORROW",
AnnouncementType.HOUR: "IN 1 HOUR",
AnnouncementType.B_TIME: "NOW",
}

def announcer(time_delta):

events = get_events_in_time_delta(time_delta)

if events:
msg = f"**What's happening {times_msg[time_delta]}**"
asyncio.run_coroutine_threadsafe(
self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg),
self.loop,
).result(TIMEOUT_SECS)
print("Sending: ", time_delta) # debugging

send_embed(events)

def send_embed(events):
for event in events:
embed = discord.Embed(
title=event.name,
description=event.description,
colour=discord.Colour.red(),
)
embed.add_field(
name="Starts", value=event.get_start_date_and_time_string()
)
embed.add_field(name="Ends", value=event.get_end_date_and_time_string())
if event.link:
embed.add_field(name="Link", value=event.link, inline=False)
asyncio.run_coroutine_threadsafe(
self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(embed=embed),
self.loop,
).result(TIMEOUT_SECS)

if settings.DEBUG:
schedule.every(10).seconds.do(partial(announcer, AnnouncementType.WEEK))
schedule.every(10).seconds.do(partial(announcer, AnnouncementType.TOMORROW))
schedule.every(10).seconds.do(partial(announcer, AnnouncementType.TODAY))
schedule.every(10).seconds.do(partial(announcer, AnnouncementType.HOUR))
schedule.every(10).seconds.do(partial(announcer, AnnouncementType.B_TIME))
else:
schedule.every().sunday.at("17:00").do(
partial(announcer, AnnouncementType.WEEK)
)
schedule.every().day.at("08:00").do(
partial(announcer, AnnouncementType.TOMORROW)
)
schedule.every().day.at("08:00").do(
partial(announcer, AnnouncementType.TODAY)
)
schedule.every().hour.do(partial(announcer, AnnouncementType.HOUR))
schedule.every(10).minutes.do(partial(announcer, AnnouncementType.B_TIME))

while True:
schedule.run_pending()
time.sleep(5)


if TOKEN:
csua_bot = CSUABot()
Expand Down
30 changes: 30 additions & 0 deletions apps/discordbot/migrations/0005_auto_20210520_2001.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 2.2.19 on 2021-05-21 03:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [("discordbot", "0004_auto_20210321_0152")]

operations = [
migrations.AlterField(
model_name="connectfourgame",
name="player1",
field=models.BigIntegerField(help_text="Discord User ID of player 1"),
),
migrations.AlterField(
model_name="connectfourgame",
name="player2",
field=models.BigIntegerField(help_text="Discord User ID of player 2"),
),
migrations.AlterField(
model_name="connectfourgame",
name="winner",
field=models.IntegerField(
blank=True,
help_text="Null if no winner, 1 or 2 if winner exists",
null=True,
),
),
]
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ slack_bolt

# apps.discordbot
discord.py
schedule
pyfiglet
cowpy

Expand Down
2 changes: 1 addition & 1 deletion templates/upcoming_events.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% for event in events %}
<li>
<strong>{{ event.name }}</strong><br>
{{ event.date|date:"l, N j, Y" }} {{ event.time }}
{{ event.get_start_date_and_time_string }}
<span class="icon is-small">
<a href="{{ event.link }}">
<i class="fas fa-link"></i>
Expand Down

0 comments on commit 2e2f937

Please sign in to comment.