From d37cdb63890bc6d3d5ed887bda5658d8f34c2cff Mon Sep 17 00:00:00 2001 From: royh02 Date: Sat, 27 Feb 2021 23:53:16 -0800 Subject: [PATCH 01/11] update repo + debugging event announcements --- apps/discordbot/annoucements.py | 123 ++++++++++++++++++++++++++++++++ apps/discordbot/bot.py | 33 ++++++++- csua | 0 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 apps/discordbot/annoucements.py create mode 100644 csua diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py new file mode 100644 index 0000000..41206e3 --- /dev/null +++ b/apps/discordbot/annoucements.py @@ -0,0 +1,123 @@ +import datetime +from apps.db_data.models import Event + +_MONTHNAMES = [None, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', + 'September', 'October', 'November', 'December'] + + +def posixtime_to_str(posix_time): + """Takes a posixtime and returns its representation in the following format: + , . + Year 0 does not exist so it is used to indicate a date with no definite year. + """ + dt = datetime.datetime.utcfromtimestamp(posix_time) + suffix = 'th' + if dt.day % 10 == 1: + suffix = 'st' + elif dt.day % 10 == 2: + suffix = 'nd' + elif dt.day % 10 == 3: + suffix = 'rd' + + date_string = f"{_MONTHNAMES[dt.month]} {dt.day}{suffix}" + + # Only nonzero years are displayed + if dt.year: + date_string = f"{date_string}, {dt.year}" + + return date_string + +# An implementation of current DST (Daylight Savings Time) rules for major US time zones 2007 and later. +# Adapted from: https://docs.python.org/3/library/datetime.html#datetime.tzinfo + + +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) +SECOND = datetime.timedelta(seconds=1) + + +def first_sunday_on_or_after(dt): + """Finds the first sunday on or after a specific datetime. + """ + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += datetime.timedelta(days_to_go) + return dt + + +def us_dst_range(year): + """ Returns start and end times for US DST on a given year after 2006. + --------------------------------------------------------------------- + US DST Rules + This is a simplified (i.e., wrong for a few cases) set of rules for US + DST start and end times. For a complete and up-to-date set of DST rules + and timezone definitions, visit the Olson Database (or try pytz): + http://www.twinsun.com/tz/tz-link.htm + http://sourceforge.net/projects/pytz/ (might not be up-to-date) + In the US, since 2007, DST starts at 2am (standard time) on the second + Sunday in March, which is the first Sunday on or after Mar 8. + and ends at 2am (DST time) on the first Sunday of Nov. + """ + start = first_sunday_on_or_after( + datetime.datetime(1, 3, 8, 2).replace(year=year)) + end = first_sunday_on_or_after( + datetime.datetime(1, 11, 1, 2).replace(year=year)) + return start, end + + +class USTimeZone(datetime.tzinfo): + """A Class representing a USTimeZone, complete with DST. Inherits from datetime.tzinfo. + """ + + def __init__(self, hours): + self.stdoffset = datetime.timedelta(hours=hours) + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + """Calculates the timezone offset due to DST. Overriden from the tzinfo interface. + """ + assert dt is not None and dt.tzinfo is self, "Invalid Datetime Object" + start, end = us_dst_range(dt.year) + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + dt = dt.replace(tzinfo=None) + if start + HOUR <= dt < end - HOUR: + # DST is in effect. + return HOUR + if end - HOUR <= dt < end: + # Fold (an ambiguous hour): use dt.fold to disambiguate. + return ZERO if dt.fold else HOUR + if start <= dt < start + HOUR: + # Gap (a non-existent hour): reverse the fold rule. + return HOUR if dt.fold else ZERO + # DST is off. + return ZERO + + def fromutc(self, dt): + """Overriding the fromutc function in tzinfo. + """ + assert dt.tzinfo is self + start, end = us_dst_range(dt.year) + start = start.replace(tzinfo=self) + end = end.replace(tzinfo=self) + std_time = dt + self.stdoffset + dst_time = std_time + HOUR + if end <= dst_time < end + HOUR: + # Repeated hour + return std_time.replace(fold=1) + if std_time < start or dst_time >= end: + # Standard time + return std_time + if start <= std_time < end - HOUR: + # Daylight savings time + return dst_time + +def event_checker(): + """ + Checks events in db to see if any match the day + """ + today = datetime.date.today() + events = Event.objects.filter(date=today).order_by("time") + return events diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 836bd7e..a556675 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -2,13 +2,18 @@ import threading import asyncio import unicodedata +import time from decouple import config import discord +from discord import channel +from discord.embeds import Embed from discord.utils import get +from discord.ext import tasks, commands from django.core.validators import validate_email from django.core.exceptions import ValidationError from .utils import send_verify_mail +from .annoucements import event_checker intents = discord.Intents.all() intents.presences = False @@ -35,6 +40,7 @@ async def on_ready(self): self.hoser_role = get(self.csua_guild.roles, id=HOSER_ROLE_ID) # if self.csua_guild is not None and self.test_channel is not None and self.hoser_role is not None: # await self.test_channel.send("booting up successfully into phillip_debug channel") + event_checker() async def verify_member_email(self, user): channel = user.dm_channel @@ -117,6 +123,8 @@ def check_thumb(react, _): if self.is_phillip: await self.test_channel.send(f"{member} was sent registration email") + + def emoji_letters(chars): return [unicodedata.lookup(f"REGIONAL INDICATOR SYMBOL LETTER {c}") for c in chars] @@ -135,13 +143,14 @@ class CSUABot: def __init__(self): self.loop = asyncio.new_event_loop() self.thread = threading.Thread(target=self._start, daemon=True) + self.announcements_thread = threading.Thread(target=self.event_announcement, daemon=True) self.running = True self.thread.start() def _start(self): asyncio.set_event_loop(self.loop) self.client = CSUAClient(intents=intents) - + self.announcements_thread.start() try: self.loop.run_until_complete(self.client.start(TOKEN)) finally: @@ -163,6 +172,28 @@ def promote_user_to_hoser(self, tag): return True return False + def event_announcement(self): + # for debugging + msg_channel = self.client.get_channel(805590450136154125) + print('hey hey hey time to check') + + while True: + time.sleep(10) + print('it worked?!?!') + events = event_checker() + for event in events: + embed = discord.Embed( + title=event.name, + description=event.description, + colour=discord.Colour.red() + ) + embed.add_field(name='Date', value=event.date, inline=True) + embed.add_field(name='Time', value=event.time) + embed.add_field(name='Link', value=event.link) + asyncio.run_coroutine_threadsafe( + self.client.get_channel(805590450136154125).send(embed=embed), self.loop + ).result(TIMEOUT_SECS) + if TOKEN: csua_bot = CSUABot() diff --git a/csua b/csua new file mode 100644 index 0000000..e69de29 From f4691b62a114713a0bbd3ebaad4a5ea5b5bdb871 Mon Sep 17 00:00:00 2001 From: royh02 Date: Wed, 3 Mar 2021 18:55:21 -0800 Subject: [PATCH 02/11] temp update with broken announce --- apps/db_data/models.py | 5 +++- apps/discordbot/annoucements.py | 18 ++++++++++++-- apps/discordbot/bot.py | 42 ++++++++++++++++++++++++--------- requirements.txt | 1 + 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/apps/db_data/models.py b/apps/db_data/models.py index bb96a08..6513684 100644 --- a/apps/db_data/models.py +++ b/apps/db_data/models.py @@ -169,7 +169,7 @@ 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) + time = models.TimeField(null=True) description = models.TextField() link = models.URLField(blank=True) category = models.ForeignKey( @@ -187,6 +187,9 @@ def __str__(self): def is_passed(self): return self.date < datetime.date.today() + def get_time_string(self): + print(datetime.time.strftime(self.time)) + class EventCategory(models.Model): id = models.CharField(max_length=16, primary_key=True) diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 41206e3..2340032 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -114,10 +114,24 @@ def fromutc(self, dt): # Daylight savings time return dst_time -def event_checker(): +def event_checker(requested_tdelta): """ Checks events in db to see if any match the day """ today = datetime.date.today() - events = Event.objects.filter(date=today).order_by("time") + days = { + "week": None, + "tomorrow": today + datetime.timedelta(days=1), + "today": today, + "hour": today, + "now": today + } + times = { + "week": None, + "tomorrow": today + datetime.timedelta(days=1), + "today": today, + "hour": today, + "now": today + } + events = Event.objects.filter(date=times[requested_tdelta]).order_by("time") return events diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index a556675..c9eeeab 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -2,13 +2,11 @@ import threading import asyncio import unicodedata -import time +import datetime, schedule, time from decouple import config import discord -from discord import channel from discord.embeds import Embed from discord.utils import get -from discord.ext import tasks, commands from django.core.validators import validate_email from django.core.exceptions import ValidationError @@ -40,7 +38,6 @@ async def on_ready(self): self.hoser_role = get(self.csua_guild.roles, id=HOSER_ROLE_ID) # if self.csua_guild is not None and self.test_channel is not None and self.hoser_role is not None: # await self.test_channel.send("booting up successfully into phillip_debug channel") - event_checker() async def verify_member_email(self, user): channel = user.dm_channel @@ -175,24 +172,47 @@ def promote_user_to_hoser(self, tag): def event_announcement(self): # for debugging msg_channel = self.client.get_channel(805590450136154125) - print('hey hey hey time to check') + + times_msg = { + "week": "NEXT WEEK", + "today": "TODAY", + "hour": "IN 1 HOUR", + "now": "NOW" + } + + def announcer(time_before): + msg = f"**What's happening {times_msg[time_before]}**" + asyncio.run_coroutine_threadsafe( + self.client.get_channel(805590450136154125).send(msg), self.loop + ).result(TIMEOUT_SECS) + print('hey hey hey time to check') - while True: - time.sleep(10) - print('it worked?!?!') - events = event_checker() + events = event_checker(time_before) + send_embed(events) + + def send_embed(events): for event in events: embed = discord.Embed( - title=event.name, + title=f"[{event.name}]({event.link})", description=event.description, colour=discord.Colour.red() ) embed.add_field(name='Date', value=event.date, inline=True) embed.add_field(name='Time', value=event.time) - embed.add_field(name='Link', value=event.link) + # embed.add_field(name='Link', value=event.link) asyncio.run_coroutine_threadsafe( self.client.get_channel(805590450136154125).send(embed=embed), self.loop ).result(TIMEOUT_SECS) + + schedule.every(2).seconds.do(announcer("today")) + + # schedule.every().day.at("8:00").do(daily_announce) + + while True: + schedule.run_pending() + print('it worked?!?!') + time.sleep(5) + if TOKEN: diff --git a/requirements.txt b/requirements.txt index 0cac47e..01f06f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ slack_bolt # apps.discordbot discord.py +schedule \ No newline at end of file From a17e20da63ec0933f44170a91037cfc352001893 Mon Sep 17 00:00:00 2001 From: royh02 Date: Sun, 25 Apr 2021 00:26:23 -0700 Subject: [PATCH 03/11] Updated bot to include announcement feature Now, In 1 hour, Today, Tomorrow are done. Week is not --- apps/db_data/models.py | 2 +- apps/discordbot/annoucements.py | 134 +++----------------------------- apps/discordbot/bot.py | 42 +++++----- 3 files changed, 37 insertions(+), 141 deletions(-) diff --git a/apps/db_data/models.py b/apps/db_data/models.py index 6513684..f53609c 100644 --- a/apps/db_data/models.py +++ b/apps/db_data/models.py @@ -188,7 +188,7 @@ def is_passed(self): return self.date < datetime.date.today() def get_time_string(self): - print(datetime.time.strftime(self.time)) + return self.time.strftime("%I:%M %p PST") class EventCategory(models.Model): diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 2340032..58f1aa0 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -1,137 +1,27 @@ import datetime from apps.db_data.models import Event -_MONTHNAMES = [None, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', - 'September', 'October', 'November', 'December'] - - -def posixtime_to_str(posix_time): - """Takes a posixtime and returns its representation in the following format: - , . - Year 0 does not exist so it is used to indicate a date with no definite year. - """ - dt = datetime.datetime.utcfromtimestamp(posix_time) - suffix = 'th' - if dt.day % 10 == 1: - suffix = 'st' - elif dt.day % 10 == 2: - suffix = 'nd' - elif dt.day % 10 == 3: - suffix = 'rd' - - date_string = f"{_MONTHNAMES[dt.month]} {dt.day}{suffix}" - - # Only nonzero years are displayed - if dt.year: - date_string = f"{date_string}, {dt.year}" - - return date_string - -# An implementation of current DST (Daylight Savings Time) rules for major US time zones 2007 and later. -# Adapted from: https://docs.python.org/3/library/datetime.html#datetime.tzinfo - - -ZERO = datetime.timedelta(0) -HOUR = datetime.timedelta(hours=1) -SECOND = datetime.timedelta(seconds=1) - - -def first_sunday_on_or_after(dt): - """Finds the first sunday on or after a specific datetime. - """ - days_to_go = 6 - dt.weekday() - if days_to_go: - dt += datetime.timedelta(days_to_go) - return dt - - -def us_dst_range(year): - """ Returns start and end times for US DST on a given year after 2006. - --------------------------------------------------------------------- - US DST Rules - This is a simplified (i.e., wrong for a few cases) set of rules for US - DST start and end times. For a complete and up-to-date set of DST rules - and timezone definitions, visit the Olson Database (or try pytz): - http://www.twinsun.com/tz/tz-link.htm - http://sourceforge.net/projects/pytz/ (might not be up-to-date) - In the US, since 2007, DST starts at 2am (standard time) on the second - Sunday in March, which is the first Sunday on or after Mar 8. - and ends at 2am (DST time) on the first Sunday of Nov. - """ - start = first_sunday_on_or_after( - datetime.datetime(1, 3, 8, 2).replace(year=year)) - end = first_sunday_on_or_after( - datetime.datetime(1, 11, 1, 2).replace(year=year)) - return start, end - - -class USTimeZone(datetime.tzinfo): - """A Class representing a USTimeZone, complete with DST. Inherits from datetime.tzinfo. - """ - - def __init__(self, hours): - self.stdoffset = datetime.timedelta(hours=hours) - - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) - - def dst(self, dt): - """Calculates the timezone offset due to DST. Overriden from the tzinfo interface. - """ - assert dt is not None and dt.tzinfo is self, "Invalid Datetime Object" - start, end = us_dst_range(dt.year) - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - dt = dt.replace(tzinfo=None) - if start + HOUR <= dt < end - HOUR: - # DST is in effect. - return HOUR - if end - HOUR <= dt < end: - # Fold (an ambiguous hour): use dt.fold to disambiguate. - return ZERO if dt.fold else HOUR - if start <= dt < start + HOUR: - # Gap (a non-existent hour): reverse the fold rule. - return HOUR if dt.fold else ZERO - # DST is off. - return ZERO - - def fromutc(self, dt): - """Overriding the fromutc function in tzinfo. - """ - assert dt.tzinfo is self - start, end = us_dst_range(dt.year) - start = start.replace(tzinfo=self) - end = end.replace(tzinfo=self) - std_time = dt + self.stdoffset - dst_time = std_time + HOUR - if end <= dst_time < end + HOUR: - # Repeated hour - return std_time.replace(fold=1) - if std_time < start or dst_time >= end: - # Standard time - return std_time - if start <= std_time < end - HOUR: - # Daylight savings time - return dst_time - def event_checker(requested_tdelta): """ Checks events in db to see if any match the day """ today = datetime.date.today() + now = datetime.datetime.now() + upcoming_events = Event.objects.filter(date__gte=today).filter(time__gte=now) days = { - "week": None, + "week": today + datetime.timedelta(weeks=1), "tomorrow": today + datetime.timedelta(days=1), "today": today, - "hour": today, - "now": today } times = { - "week": None, - "tomorrow": today + datetime.timedelta(days=1), - "today": today, - "hour": today, - "now": today + "hour": now + datetime.timedelta(hours=1), + "now": now + datetime.timedelta(minutes=1) } - events = Event.objects.filter(date=times[requested_tdelta]).order_by("time") + if requested_tdelta in times: + events = Event.objects \ + .filter(date=today, time__lte=times[requested_tdelta]) \ + .filter(time__gte=now) \ + .order_by("time") + else: + events = Event.objects.filter(date=days[requested_tdelta]).order_by("time") return events diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index c9eeeab..6d4e3da 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -2,6 +2,7 @@ import threading import asyncio import unicodedata +from functools import partial import datetime, schedule, time from decouple import config import discord @@ -31,6 +32,8 @@ 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: print("Phillip is in the Office") self.csua_guild = get(self.guilds, id=CSUA_GUILD_ID) @@ -140,14 +143,13 @@ class CSUABot: def __init__(self): self.loop = asyncio.new_event_loop() self.thread = threading.Thread(target=self._start, daemon=True) - self.announcements_thread = threading.Thread(target=self.event_announcement, daemon=True) + self.running = True self.thread.start() def _start(self): asyncio.set_event_loop(self.loop) self.client = CSUAClient(intents=intents) - self.announcements_thread.start() try: self.loop.run_until_complete(self.client.start(TOKEN)) finally: @@ -170,47 +172,51 @@ def promote_user_to_hoser(self, tag): return False def event_announcement(self): - # for debugging - msg_channel = self.client.get_channel(805590450136154125) + print('Announcements Thread started...') times_msg = { "week": "NEXT WEEK", "today": "TODAY", + "tomorrow": "TOMORROW", "hour": "IN 1 HOUR", - "now": "NOW" + "now": "NOW", } def announcer(time_before): - msg = f"**What's happening {times_msg[time_before]}**" - asyncio.run_coroutine_threadsafe( - self.client.get_channel(805590450136154125).send(msg), self.loop - ).result(TIMEOUT_SECS) - print('hey hey hey time to check') events = event_checker(time_before) - send_embed(events) + + if events: + msg = f"**What's happening {times_msg[time_before]}**" + asyncio.run_coroutine_threadsafe( + self.client.get_channel(805590450136154125).send(msg), self.loop + ).result(TIMEOUT_SECS) + print('hey hey hey time to check') # debugging + + send_embed(events) def send_embed(events): for event in events: embed = discord.Embed( - title=f"[{event.name}]({event.link})", + title=event.name, description=event.description, colour=discord.Colour.red() ) embed.add_field(name='Date', value=event.date, inline=True) - embed.add_field(name='Time', value=event.time) - # embed.add_field(name='Link', value=event.link) + embed.add_field(name='Time', value=event.get_time_string()) + embed.add_field(name='Link', value=event.link) asyncio.run_coroutine_threadsafe( self.client.get_channel(805590450136154125).send(embed=embed), self.loop ).result(TIMEOUT_SECS) - schedule.every(2).seconds.do(announcer("today")) - - # schedule.every().day.at("8:00").do(daily_announce) + + schedule.every().day.at("08:00").do(partial(announcer, "tomorrow")) + schedule.every().day.at("08:00").do(partial(announcer, "today")) + schedule.every().hour.do(partial(announcer, "hour")) + schedule.every(30).minutes.do(partial(announcer, "now")) while True: schedule.run_pending() - print('it worked?!?!') time.sleep(5) From 77117a7c0835ef14a952e20e7ec3e9a4ebf6d9ca Mon Sep 17 00:00:00 2001 From: royh02 Date: Sun, 25 Apr 2021 00:44:48 -0700 Subject: [PATCH 04/11] weekly announcement works now --- apps/discordbot/annoucements.py | 3 +++ apps/discordbot/bot.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 58f1aa0..7c053dd 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -22,6 +22,9 @@ def event_checker(requested_tdelta): .filter(date=today, time__lte=times[requested_tdelta]) \ .filter(time__gte=now) \ .order_by("time") + elif requested_tdelta == "week": + events = Event.objects.filter( + date__lte=days[requested_tdelta]).filter(date__gte=today).order_by("time") else: events = Event.objects.filter(date=days[requested_tdelta]).order_by("time") return events diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 6d4e3da..b4e17eb 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -210,11 +210,19 @@ def send_embed(events): ).result(TIMEOUT_SECS) + schedule.every().sunday.at("17:00").do(partial(announcer, "week")) schedule.every().day.at("08:00").do(partial(announcer, "tomorrow")) schedule.every().day.at("08:00").do(partial(announcer, "today")) schedule.every().hour.do(partial(announcer, "hour")) schedule.every(30).minutes.do(partial(announcer, "now")) + # For debugging + # schedule.every(10).seconds.do(partial(announcer, "week")) + # schedule.every(10).seconds.do(partial(announcer, "tomorrow")) + # schedule.every(10).seconds.do(partial(announcer, "today")) + # schedule.every(10).seconds.do(partial(announcer, "hour")) + # schedule.every(10).seconds.do(partial(announcer, "now")) + while True: schedule.run_pending() time.sleep(5) From 6d348576c158f08e5627fbe02c4780ff28e31b1e Mon Sep 17 00:00:00 2001 From: royh02 Date: Sun, 25 Apr 2021 14:58:36 -0700 Subject: [PATCH 05/11] announcement channel id added for testing currently just chatter --- apps/discordbot/bot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index b4e17eb..9afeae4 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -22,6 +22,7 @@ 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 TIMEOUT_SECS = 10 ANI_NRUSIMHA_ID = 168539105704017920 @@ -189,7 +190,7 @@ def announcer(time_before): if events: msg = f"**What's happening {times_msg[time_before]}**" asyncio.run_coroutine_threadsafe( - self.client.get_channel(805590450136154125).send(msg), self.loop + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg), self.loop ).result(TIMEOUT_SECS) print('hey hey hey time to check') # debugging @@ -206,7 +207,7 @@ def send_embed(events): embed.add_field(name='Time', value=event.get_time_string()) embed.add_field(name='Link', value=event.link) asyncio.run_coroutine_threadsafe( - self.client.get_channel(805590450136154125).send(embed=embed), self.loop + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(embed=embed), self.loop ).result(TIMEOUT_SECS) From 480a860d85b543bd22f63774c876218f5b723f68 Mon Sep 17 00:00:00 2001 From: royh02 Date: Sun, 16 May 2021 16:58:36 -0700 Subject: [PATCH 06/11] Migrated database + Fixed timezones + Refactored --- .gitignore | 1 + apps/csua_backend/wsgi.py | 7 +++ .../migrations/0021_auto_20210515_1807.py | 26 ++++++++ apps/db_data/models.py | 20 ++++--- apps/db_data/templatetags/db_data_tags.py | 2 +- apps/discordbot/annoucements.py | 59 +++++++++++-------- apps/discordbot/bot.py | 33 ++++++----- templates/upcoming_events.html | 2 +- 8 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 apps/db_data/migrations/0021_auto_20210515_1807.py diff --git a/.gitignore b/.gitignore index 77365fe..20d5241 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,4 @@ data/ # Virtual environment /venv +.vscode/settings.json diff --git a/apps/csua_backend/wsgi.py b/apps/csua_backend/wsgi.py index d51ee89..e83b22f 100644 --- a/apps/csua_backend/wsgi.py +++ b/apps/csua_backend/wsgi.py @@ -13,6 +13,7 @@ framework. """ +from apps.discordbot.bot import csua_bot import os # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks @@ -31,3 +32,9 @@ # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) + +# We start discordbot thread here so that it doesn't interfere with other django +# commands such as test and migrate + +if csua_bot: + csua_bot.thread.start() diff --git a/apps/db_data/migrations/0021_auto_20210515_1807.py b/apps/db_data/migrations/0021_auto_20210515_1807.py new file mode 100644 index 0000000..8ac3da4 --- /dev/null +++ b/apps/db_data/migrations/0021_auto_20210515_1807.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.19 on 2021-05-16 01:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db_data', '0020_auto_20200728_2346'), + ] + + operations = [ + migrations.RemoveField( + model_name='event', + name='date', + ), + migrations.RemoveField( + model_name='event', + name='time', + ), + migrations.AddField( + model_name='event', + name='date_time', + field=models.DateTimeField(null=True), + ), + ] diff --git a/apps/db_data/models.py b/apps/db_data/models.py index f53609c..dceda29 100644 --- a/apps/db_data/models.py +++ b/apps/db_data/models.py @@ -1,5 +1,6 @@ import os import datetime +import pytz from django.db import models from django.contrib.auth.models import User as DjangoUser @@ -168,8 +169,7 @@ 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.TimeField(null=True) + date_time = models.DateTimeField(null=False) description = models.TextField() link = models.URLField(blank=True) category = models.ForeignKey( @@ -179,16 +179,22 @@ class Event(models.Model): on_delete=models.PROTECT, help_text="Currently unused.", ) - - def __str__(self): - return f"{self.name} ({self.date})" + ordering = ['date_time'] @property def is_passed(self): - return self.date < datetime.date.today() + return self.date_time < pytz.timezone.now() def get_time_string(self): - return self.time.strftime("%I:%M %p PST") + return self.date_time.astimezone(pytz.timezone('US/Pacific')).strftime("%I:%M %p %Z") + + def get_date_string(self): + return self.date_time.astimezone(pytz.timezone('US/Pacific')).strftime("%x") + + def __str__(self): + if not self.date_time: + return f"{self.name}" + return f"{self.name} ({self.get_date_string()})" class EventCategory(models.Model): diff --git a/apps/db_data/templatetags/db_data_tags.py b/apps/db_data/templatetags/db_data_tags.py index d8aad5a..5984a36 100644 --- a/apps/db_data/templatetags/db_data_tags.py +++ b/apps/db_data/templatetags/db_data_tags.py @@ -8,7 +8,7 @@ @register.simple_tag def get_upcoming_events(): - return Event.objects.filter(date__gte=datetime.date.today()) + return Event.objects.filter(date_time__gte=datetime.datetime.today()) @register.simple_tag diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 7c053dd..13ecbe2 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -1,30 +1,43 @@ +from django.utils import timezone +import pytz import datetime from apps.db_data.models import Event -def event_checker(requested_tdelta): +def get_events_in_date_or_time_delta(requested_tdelta): """ Checks events in db to see if any match the day """ - today = datetime.date.today() - now = datetime.datetime.now() - upcoming_events = Event.objects.filter(date__gte=today).filter(time__gte=now) - days = { - "week": today + datetime.timedelta(weeks=1), - "tomorrow": today + datetime.timedelta(days=1), - "today": today, - } - times = { - "hour": now + datetime.timedelta(hours=1), - "now": now + datetime.timedelta(minutes=1) - } - if requested_tdelta in times: - events = Event.objects \ - .filter(date=today, time__lte=times[requested_tdelta]) \ - .filter(time__gte=now) \ - .order_by("time") - elif requested_tdelta == "week": - events = Event.objects.filter( - date__lte=days[requested_tdelta]).filter(date__gte=today).order_by("time") - else: - events = Event.objects.filter(date=days[requested_tdelta]).order_by("time") + now = timezone.now().astimezone(pytz.timezone('US/Pacific')) + events = get_events_in_time_range(timeify(requested_tdelta)) return events + + +def get_events_in_time_range(range): + """ + Takes in a tuple from timeify + Returns events in the datetime range + """ + events = Event.objects.filter(date_time__gte=range[0]) \ + .filter(date_time__lte=range[1]) + return events + +def timeify(requested_tdelta): + """ + Converts requested_tdelta string into a corresponding datetime object + """ + now = timezone.now() + end_times = { + "week": now + datetime.timedelta(weeks=1), + "tomorrow": (now + datetime.timedelta(days=1)).replace(hour=23, minute=59, second=59), + "today": now.replace(hour=23, minute=59, second=59), + "hour": (now + datetime.timedelta(hours=1)).replace(minute=59, second=59), + "now": now + datetime.timedelta(minutes=10) + } + start_times = { + "week": now, + "tomorrow": (now + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0), + "today": now, + "hour": (now + datetime.timedelta(hours=1)).replace(minute=0, second=0), + "now": now + } + return start_times[requested_tdelta], end_times[requested_tdelta] diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 9afeae4..cc9c423 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -3,7 +3,7 @@ import asyncio import unicodedata from functools import partial -import datetime, schedule, time +import schedule, time from decouple import config import discord from discord.embeds import Embed @@ -12,7 +12,7 @@ from django.core.exceptions import ValidationError from .utils import send_verify_mail -from .annoucements import event_checker +from .annoucements import get_events_in_date_or_time_delta intents = discord.Intents.all() intents.presences = False @@ -23,6 +23,7 @@ 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 ANI_NRUSIMHA_ID = 168539105704017920 @@ -144,9 +145,6 @@ class CSUABot: def __init__(self): self.loop = asyncio.new_event_loop() self.thread = threading.Thread(target=self._start, daemon=True) - - self.running = True - self.thread.start() def _start(self): asyncio.set_event_loop(self.loop) @@ -174,7 +172,13 @@ def promote_user_to_hoser(self, tag): def event_announcement(self): print('Announcements Thread started...') - + + WEEK = "week" + TOMORROW = "tomorrow" + TODAY = "today" + HOUR = "hour" + NOW = "now" + times_msg = { "week": "NEXT WEEK", "today": "TODAY", @@ -185,7 +189,7 @@ def event_announcement(self): def announcer(time_before): - events = event_checker(time_before) + events = get_events_in_date_or_time_delta(time_before) if events: msg = f"**What's happening {times_msg[time_before]}**" @@ -203,19 +207,20 @@ def send_embed(events): description=event.description, colour=discord.Colour.red() ) - embed.add_field(name='Date', value=event.date, inline=True) + embed.add_field(name='Date', value=event.get_date_string(), inline=True) embed.add_field(name='Time', value=event.get_time_string()) embed.add_field(name='Link', value=event.link) asyncio.run_coroutine_threadsafe( - self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(embed=embed), self.loop + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send( + embed=embed), self.loop ).result(TIMEOUT_SECS) - schedule.every().sunday.at("17:00").do(partial(announcer, "week")) - schedule.every().day.at("08:00").do(partial(announcer, "tomorrow")) - schedule.every().day.at("08:00").do(partial(announcer, "today")) - schedule.every().hour.do(partial(announcer, "hour")) - schedule.every(30).minutes.do(partial(announcer, "now")) + schedule.every().sunday.at("17:00").do(partial(announcer, WEEK)) + schedule.every().day.at("08:00").do(partial(announcer, TOMORROW)) + schedule.every().day.at("08:00").do(partial(announcer, TODAY)) + schedule.every().hour.do(partial(announcer, HOUR)) + schedule.every(30).minutes.do(partial(announcer, NOW)) # For debugging # schedule.every(10).seconds.do(partial(announcer, "week")) diff --git a/templates/upcoming_events.html b/templates/upcoming_events.html index 3cd0d9c..46ca7ab 100644 --- a/templates/upcoming_events.html +++ b/templates/upcoming_events.html @@ -6,7 +6,7 @@ {% for event in events %}
  • {{ event.name }}
    - {{ event.date|date:"l, N j, Y" }} {{ event.time }} + {{ event.get_date_string}} {{ event.get_time_string }} From ab0875fc5f5504a402d73c38be8400e3b9802dc0 Mon Sep 17 00:00:00 2001 From: Roy Huang Date: Mon, 17 May 2021 02:27:31 -0700 Subject: [PATCH 07/11] black format --- apps/slackbot/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/slackbot/views.py b/apps/slackbot/views.py index d829eb5..c284295 100644 --- a/apps/slackbot/views.py +++ b/apps/slackbot/views.py @@ -3,9 +3,13 @@ from .client import app -handler = SlackRequestHandler(app) +if app is not None: + handler = SlackRequestHandler(app) +else: + handler = None @csrf_exempt def events(request): - return handler.handle(request) + if handler is not None: + return handler.handle(request) From 86c479e9c77131c64f7fa646ba6038f0444b5066 Mon Sep 17 00:00:00 2001 From: Roy Huang Date: Mon, 17 May 2021 02:29:06 -0700 Subject: [PATCH 08/11] black format again --- apps/csua_backend/wsgi.py | 3 +- .../migrations/0021_auto_20210515_1807.py | 18 ++----- apps/db_data/models.py | 10 ++-- apps/discordbot/annoucements.py | 27 +++++++---- apps/discordbot/bot.py | 47 ++++++++++--------- 5 files changed, 55 insertions(+), 50 deletions(-) diff --git a/apps/csua_backend/wsgi.py b/apps/csua_backend/wsgi.py index d24e39a..3407a51 100644 --- a/apps/csua_backend/wsgi.py +++ b/apps/csua_backend/wsgi.py @@ -13,9 +13,10 @@ framework. """ -from apps.discordbot.bot import csua_bot 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 diff --git a/apps/db_data/migrations/0021_auto_20210515_1807.py b/apps/db_data/migrations/0021_auto_20210515_1807.py index 8ac3da4..4c69599 100644 --- a/apps/db_data/migrations/0021_auto_20210515_1807.py +++ b/apps/db_data/migrations/0021_auto_20210515_1807.py @@ -5,22 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('db_data', '0020_auto_20200728_2346'), - ] + dependencies = [("db_data", "0020_auto_20200728_2346")] operations = [ - migrations.RemoveField( - model_name='event', - name='date', - ), - migrations.RemoveField( - model_name='event', - name='time', - ), + migrations.RemoveField(model_name="event", name="date"), + migrations.RemoveField(model_name="event", name="time"), migrations.AddField( - model_name='event', - name='date_time', - field=models.DateTimeField(null=True), + model_name="event", name="date_time", field=models.DateTimeField(null=True) ), ] diff --git a/apps/db_data/models.py b/apps/db_data/models.py index b2be2ab..4d9bf15 100644 --- a/apps/db_data/models.py +++ b/apps/db_data/models.py @@ -1,7 +1,7 @@ import datetime -import pytz import os +import pytz from django.contrib.auth.models import User as DjangoUser from django.db import models @@ -179,17 +179,19 @@ class Event(models.Model): on_delete=models.PROTECT, help_text="Currently unused.", ) - ordering = ['date_time'] + ordering = ["date_time"] @property def is_passed(self): return self.date_time < pytz.timezone.now() def get_time_string(self): - return self.date_time.astimezone(pytz.timezone('US/Pacific')).strftime("%I:%M %p %Z") + return self.date_time.astimezone(pytz.timezone("US/Pacific")).strftime( + "%I:%M %p %Z" + ) def get_date_string(self): - return self.date_time.astimezone(pytz.timezone('US/Pacific')).strftime("%x") + return self.date_time.astimezone(pytz.timezone("US/Pacific")).strftime("%x") def __str__(self): if not self.date_time: diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 13ecbe2..588c1f0 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -1,13 +1,16 @@ -from django.utils import timezone -import pytz import datetime + +import pytz +from django.utils import timezone + from apps.db_data.models import Event + def get_events_in_date_or_time_delta(requested_tdelta): """ Checks events in db to see if any match the day """ - now = timezone.now().astimezone(pytz.timezone('US/Pacific')) + now = timezone.now().astimezone(pytz.timezone("US/Pacific")) events = get_events_in_time_range(timeify(requested_tdelta)) return events @@ -17,10 +20,12 @@ def get_events_in_time_range(range): Takes in a tuple from timeify Returns events in the datetime range """ - events = Event.objects.filter(date_time__gte=range[0]) \ - .filter(date_time__lte=range[1]) + events = Event.objects.filter(date_time__gte=range[0]).filter( + date_time__lte=range[1] + ) return events + def timeify(requested_tdelta): """ Converts requested_tdelta string into a corresponding datetime object @@ -28,16 +33,20 @@ def timeify(requested_tdelta): now = timezone.now() end_times = { "week": now + datetime.timedelta(weeks=1), - "tomorrow": (now + datetime.timedelta(days=1)).replace(hour=23, minute=59, second=59), + "tomorrow": (now + datetime.timedelta(days=1)).replace( + hour=23, minute=59, second=59 + ), "today": now.replace(hour=23, minute=59, second=59), "hour": (now + datetime.timedelta(hours=1)).replace(minute=59, second=59), - "now": now + datetime.timedelta(minutes=10) + "now": now + datetime.timedelta(minutes=10), } start_times = { "week": now, - "tomorrow": (now + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0), + "tomorrow": (now + datetime.timedelta(days=1)).replace( + hour=0, minute=0, second=0 + ), "today": now, "hour": (now + datetime.timedelta(hours=1)).replace(minute=0, second=0), - "now": now + "now": now, } return start_times[requested_tdelta], end_times[requested_tdelta] diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 1835f3b..3e60fce 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -1,11 +1,13 @@ import asyncio import logging import threading +import time import unicodedata from functools import partial -import schedule, time -from decouple import config + import discord +import schedule +from decouple import config from discord.embeds import Embed from discord.utils import get from django.core.exceptions import ValidationError @@ -13,8 +15,8 @@ from pyfiglet import figlet_format from . import connect4, cowsay, xkcd -from .utils import send_verify_mail from .annoucements import get_events_in_date_or_time_delta +from .utils import send_verify_mail intents = discord.Intents.all() intents.presences = False @@ -24,7 +26,9 @@ 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 +ANNOUNCEMENTS_CHANNEL_ID = config( + "ANNOUNCEMENTS_CHANNEL", default=784902200102354989, cast=int +) # set to chatter for testing ROY_TEST_SERVER_CHANNEL_ID = 805590450136154125 TIMEOUT_SECS = 10 @@ -35,7 +39,9 @@ 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 = 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) @@ -148,8 +154,6 @@ def check_thumb(react, _): if self.is_phillip: await self.test_channel.send(f"{member} was sent registration email") - - def emoji_letters(chars): return [unicodedata.lookup(f"REGIONAL INDICATOR SYMBOL LETTER {c}") for c in chars] @@ -197,7 +201,7 @@ def promote_user_to_hoser(self, tag): return False def event_announcement(self): - print('Announcements Thread started...') + print("Announcements Thread started...") WEEK = "week" TOMORROW = "tomorrow" @@ -212,7 +216,7 @@ def event_announcement(self): "hour": "IN 1 HOUR", "now": "NOW", } - + def announcer(time_before): events = get_events_in_date_or_time_delta(time_before) @@ -220,28 +224,28 @@ def announcer(time_before): if events: msg = f"**What's happening {times_msg[time_before]}**" asyncio.run_coroutine_threadsafe( - self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg), self.loop - ).result(TIMEOUT_SECS) - print('hey hey hey time to check') # debugging + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg), + self.loop, + ).result(TIMEOUT_SECS) + print("hey hey hey time to check") # 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() + colour=discord.Colour.red(), ) - embed.add_field(name='Date', value=event.get_date_string(), inline=True) - embed.add_field(name='Time', value=event.get_time_string()) - embed.add_field(name='Link', value=event.link) + embed.add_field(name="Date", value=event.get_date_string(), inline=True) + embed.add_field(name="Time", value=event.get_time_string()) + embed.add_field(name="Link", value=event.link) asyncio.run_coroutine_threadsafe( - self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send( - embed=embed), self.loop + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(embed=embed), + self.loop, ).result(TIMEOUT_SECS) - schedule.every().sunday.at("17:00").do(partial(announcer, WEEK)) schedule.every().day.at("08:00").do(partial(announcer, TOMORROW)) schedule.every().day.at("08:00").do(partial(announcer, TODAY)) @@ -258,8 +262,7 @@ def send_embed(events): while True: schedule.run_pending() time.sleep(5) - - + if TOKEN: csua_bot = CSUABot() From b25d2089fa855a036d3edcd05ac9c858e67f2453 Mon Sep 17 00:00:00 2001 From: Roy Huang Date: Sat, 29 May 2021 22:17:58 -0700 Subject: [PATCH 09/11] separated start/end times, fixed timedelta logic, removed pytz --- apps/csua_backend/wsgi.py | 1 - .../migrations/0022_auto_20210520_2001.py | 17 ++++++++ apps/db_data/models.py | 25 ++++++------ apps/db_data/templatetags/db_data_tags.py | 2 +- apps/discordbot/annoucements.py | 39 ++++++++++--------- apps/discordbot/bot.py | 38 ++++++++++-------- .../migrations/0005_auto_20210520_2001.py | 30 ++++++++++++++ templates/upcoming_events.html | 2 +- 8 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 apps/db_data/migrations/0022_auto_20210520_2001.py create mode 100644 apps/discordbot/migrations/0005_auto_20210520_2001.py diff --git a/apps/csua_backend/wsgi.py b/apps/csua_backend/wsgi.py index 3407a51..7397eb4 100644 --- a/apps/csua_backend/wsgi.py +++ b/apps/csua_backend/wsgi.py @@ -36,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() diff --git a/apps/db_data/migrations/0022_auto_20210520_2001.py b/apps/db_data/migrations/0022_auto_20210520_2001.py new file mode 100644 index 0000000..f8df486 --- /dev/null +++ b/apps/db_data/migrations/0022_auto_20210520_2001.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.19 on 2021-05-21 03:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("db_data", "0021_auto_20210515_1807")] + + operations = [ + migrations.RenameField( + model_name="event", old_name="date_time", new_name="start_time" + ), + migrations.AddField( + model_name="event", name="end_time", field=models.DateTimeField(null=True) + ), + ] diff --git a/apps/db_data/models.py b/apps/db_data/models.py index 4d9bf15..283be43 100644 --- a/apps/db_data/models.py +++ b/apps/db_data/models.py @@ -1,9 +1,9 @@ import datetime import os -import pytz from django.contrib.auth.models import User as DjangoUser from django.db import models +from django.utils import timezone class Semester(models.Model): @@ -169,7 +169,8 @@ class Sponsorship(models.Model): class Event(models.Model): name = models.CharField(max_length=70) location = models.CharField(max_length=70) - date_time = models.DateTimeField(null=False) + start_time = models.DateTimeField(null=True) + end_time = models.DateTimeField(null=True) description = models.TextField() link = models.URLField(blank=True) category = models.ForeignKey( @@ -179,24 +180,26 @@ class Event(models.Model): on_delete=models.PROTECT, help_text="Currently unused.", ) - ordering = ["date_time"] + ordering = ["start_time"] @property def is_passed(self): - return self.date_time < pytz.timezone.now() + return self.start_time < timezone.now() - def get_time_string(self): - return self.date_time.astimezone(pytz.timezone("US/Pacific")).strftime( - "%I:%M %p %Z" + 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_date_string(self): - return self.date_time.astimezone(pytz.timezone("US/Pacific")).strftime("%x") + 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.date_time: + if not self.start_time: return f"{self.name}" - return f"{self.name} ({self.get_date_string()})" + return f"{self.name} ({self.get_start_date_and_time_string()})" class EventCategory(models.Model): diff --git a/apps/db_data/templatetags/db_data_tags.py b/apps/db_data/templatetags/db_data_tags.py index e36023f..4c57aea 100644 --- a/apps/db_data/templatetags/db_data_tags.py +++ b/apps/db_data/templatetags/db_data_tags.py @@ -9,7 +9,7 @@ @register.simple_tag def get_upcoming_events(): - return Event.objects.filter(date_time__gte=datetime.datetime.today()) + return Event.objects.filter(start_time__gte=datetime.datetime.now()) @register.simple_tag diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 588c1f0..3c6c79f 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -1,27 +1,34 @@ import datetime -import pytz from django.utils import timezone from apps.db_data.models import Event -def get_events_in_date_or_time_delta(requested_tdelta): +def get_events_in_time_delta(requested_tdelta): """ - Checks events in db to see if any match the day + Retrieves a list of Event objects within the requested + time delta. + + Handles the following requested_tdelta: + - "week" (next week) + - "tomorrow" + - "today" + - "hour" (in one hour) + - "now" (in 10 minutes) """ - now = timezone.now().astimezone(pytz.timezone("US/Pacific")) - events = get_events_in_time_range(timeify(requested_tdelta)) + now = timezone.now().astimezone(timezone.get_current_timezone()) + events = get_events_in_time_range(*timeify(requested_tdelta)) return events -def get_events_in_time_range(range): +def get_events_in_time_range(start_time, end_time): """ - Takes in a tuple from timeify - Returns events in the datetime range + Takes in a two datetime objects, start_time and end_time + Returns events within the datetime range """ - events = Event.objects.filter(date_time__gte=range[0]).filter( - date_time__lte=range[1] + events = Event.objects.filter(start_time__gte=start_time).filter( + start_time__lte=end_time ) return events @@ -33,20 +40,16 @@ def timeify(requested_tdelta): now = timezone.now() end_times = { "week": now + datetime.timedelta(weeks=1), - "tomorrow": (now + datetime.timedelta(days=1)).replace( - hour=23, minute=59, second=59 - ), + "tomorrow": now + datetime.timedelta(days=1, hours=23, minutes=59, seconds=59), "today": now.replace(hour=23, minute=59, second=59), - "hour": (now + datetime.timedelta(hours=1)).replace(minute=59, second=59), + "hour": now + datetime.timedelta(hours=1, minutes=59, seconds=59), "now": now + datetime.timedelta(minutes=10), } start_times = { "week": now, - "tomorrow": (now + datetime.timedelta(days=1)).replace( - hour=0, minute=0, second=0 - ), + "tomorrow": now + datetime.timedelta(days=1), "today": now, - "hour": (now + datetime.timedelta(hours=1)).replace(minute=0, second=0), + "hour": now + datetime.timedelta(hours=1), "now": now, } return start_times[requested_tdelta], end_times[requested_tdelta] diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 3e60fce..9f29f9e 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -15,7 +15,7 @@ from pyfiglet import figlet_format from . import connect4, cowsay, xkcd -from .annoucements import get_events_in_date_or_time_delta +from .annoucements import get_events_in_time_delta from .utils import send_verify_mail intents = discord.Intents.all() @@ -207,7 +207,7 @@ def event_announcement(self): TOMORROW = "tomorrow" TODAY = "today" HOUR = "hour" - NOW = "now" + B_TIME = "now" # Berkeley Time times_msg = { "week": "NEXT WEEK", @@ -219,12 +219,12 @@ def event_announcement(self): def announcer(time_before): - events = get_events_in_date_or_time_delta(time_before) + events = get_events_in_time_delta(time_before) if events: msg = f"**What's happening {times_msg[time_before]}**" asyncio.run_coroutine_threadsafe( - self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg), + self.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send(msg), self.loop, ).result(TIMEOUT_SECS) print("hey hey hey time to check") # debugging @@ -238,26 +238,30 @@ def send_embed(events): description=event.description, colour=discord.Colour.red(), ) - embed.add_field(name="Date", value=event.get_date_string(), inline=True) - embed.add_field(name="Time", value=event.get_time_string()) - embed.add_field(name="Link", value=event.link) + 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()) + 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.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send( + embed=embed + ), self.loop, ).result(TIMEOUT_SECS) - schedule.every().sunday.at("17:00").do(partial(announcer, WEEK)) - schedule.every().day.at("08:00").do(partial(announcer, TOMORROW)) - schedule.every().day.at("08:00").do(partial(announcer, TODAY)) - schedule.every().hour.do(partial(announcer, HOUR)) - schedule.every(30).minutes.do(partial(announcer, NOW)) + # schedule.every().sunday.at("17:00").do(partial(announcer, WEEK)) + # schedule.every().day.at("08:00").do(partial(announcer, TOMORROW)) + # schedule.every().day.at("08:00").do(partial(announcer, TODAY)) + # schedule.every().hour.do(partial(announcer, HOUR)) + # schedule.every(10).minutes.do(partial(announcer, NOW)) # For debugging # schedule.every(10).seconds.do(partial(announcer, "week")) - # schedule.every(10).seconds.do(partial(announcer, "tomorrow")) - # schedule.every(10).seconds.do(partial(announcer, "today")) - # schedule.every(10).seconds.do(partial(announcer, "hour")) - # schedule.every(10).seconds.do(partial(announcer, "now")) + schedule.every(10).seconds.do(partial(announcer, TOMORROW)) + schedule.every(10).seconds.do(partial(announcer, "today")) + schedule.every(10).seconds.do(partial(announcer, "hour")) + schedule.every(10).seconds.do(partial(announcer, "now")) while True: schedule.run_pending() diff --git a/apps/discordbot/migrations/0005_auto_20210520_2001.py b/apps/discordbot/migrations/0005_auto_20210520_2001.py new file mode 100644 index 0000000..a72e8d6 --- /dev/null +++ b/apps/discordbot/migrations/0005_auto_20210520_2001.py @@ -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, + ), + ), + ] diff --git a/templates/upcoming_events.html b/templates/upcoming_events.html index 46ca7ab..ece577c 100644 --- a/templates/upcoming_events.html +++ b/templates/upcoming_events.html @@ -6,7 +6,7 @@ {% for event in events %}
  • {{ event.name }}
    - {{ event.get_date_string}} {{ event.get_time_string }} + {{ event.get_start_date_and_time_string }}
    From f220330f9a8aa6112181cfda485b7d561d141108 Mon Sep 17 00:00:00 2001 From: Roy Huang Date: Thu, 3 Jun 2021 19:23:48 -0700 Subject: [PATCH 10/11] properly set up migration and refactored --- apps/db_data/0023_datetime_update.py | 29 +++++++++ .../migrations/0021_auto_20210515_1807.py | 20 +++++-- .../migrations/0022_auto_20210520_2001.py | 17 ------ apps/discordbot/annoucements.py | 53 +++++++++------- apps/discordbot/bot.py | 60 ++++++++++--------- csua | 0 6 files changed, 106 insertions(+), 73 deletions(-) create mode 100644 apps/db_data/0023_datetime_update.py delete mode 100644 apps/db_data/migrations/0022_auto_20210520_2001.py delete mode 100644 csua diff --git a/apps/db_data/0023_datetime_update.py b/apps/db_data/0023_datetime_update.py new file mode 100644 index 0000000..edf1b03 --- /dev/null +++ b/apps/db_data/0023_datetime_update.py @@ -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"), + ] diff --git a/apps/db_data/migrations/0021_auto_20210515_1807.py b/apps/db_data/migrations/0021_auto_20210515_1807.py index 4c69599..2f3503f 100644 --- a/apps/db_data/migrations/0021_auto_20210515_1807.py +++ b/apps/db_data/migrations/0021_auto_20210515_1807.py @@ -1,16 +1,28 @@ -# Generated by Django 2.2.19 on 2021-05-16 01:07 +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.RemoveField(model_name="event", name="date"), - migrations.RemoveField(model_name="event", name="time"), migrations.AddField( - model_name="event", name="date_time", field=models.DateTimeField(null=True) + 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"), ] diff --git a/apps/db_data/migrations/0022_auto_20210520_2001.py b/apps/db_data/migrations/0022_auto_20210520_2001.py deleted file mode 100644 index f8df486..0000000 --- a/apps/db_data/migrations/0022_auto_20210520_2001.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.19 on 2021-05-21 03:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("db_data", "0021_auto_20210515_1807")] - - operations = [ - migrations.RenameField( - model_name="event", old_name="date_time", new_name="start_time" - ), - migrations.AddField( - model_name="event", name="end_time", field=models.DateTimeField(null=True) - ), - ] diff --git a/apps/discordbot/annoucements.py b/apps/discordbot/annoucements.py index 3c6c79f..f38fd02 100644 --- a/apps/discordbot/annoucements.py +++ b/apps/discordbot/annoucements.py @@ -1,24 +1,29 @@ import datetime +from enum import Enum from django.utils import timezone from apps.db_data.models import Event -def get_events_in_time_delta(requested_tdelta): +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. - Handles the following requested_tdelta: - - "week" (next week) - - "tomorrow" - - "today" - - "hour" (in one hour) - - "now" (in 10 minutes) + 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_tdelta)) + events = get_events_in_time_range(*timeify(requested_atype)) return events @@ -33,23 +38,25 @@ def get_events_in_time_range(start_time, end_time): return events -def timeify(requested_tdelta): +def timeify(requested_atype: AnnouncementType): """ Converts requested_tdelta string into a corresponding datetime object """ now = timezone.now() - end_times = { - "week": now + datetime.timedelta(weeks=1), - "tomorrow": now + datetime.timedelta(days=1, hours=23, minutes=59, seconds=59), - "today": now.replace(hour=23, minute=59, second=59), - "hour": now + datetime.timedelta(hours=1, minutes=59, seconds=59), - "now": now + datetime.timedelta(minutes=10), - } - start_times = { - "week": now, - "tomorrow": now + datetime.timedelta(days=1), - "today": now, - "hour": now + datetime.timedelta(hours=1), - "now": 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 start_times[requested_tdelta], end_times[requested_tdelta] + return time_ranges[requested_atype] diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 9f29f9e..98858bb 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -10,12 +10,13 @@ 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 get_events_in_time_delta +from .annoucements import AnnouncementType, get_events_in_time_delta from .utils import send_verify_mail intents = discord.Intents.all() @@ -203,31 +204,25 @@ def promote_user_to_hoser(self, tag): def event_announcement(self): print("Announcements Thread started...") - WEEK = "week" - TOMORROW = "tomorrow" - TODAY = "today" - HOUR = "hour" - B_TIME = "now" # Berkeley Time - times_msg = { - "week": "NEXT WEEK", - "today": "TODAY", - "tomorrow": "TOMORROW", - "hour": "IN 1 HOUR", - "now": "NOW", + AnnouncementType.WEEK: "NEXT WEEK", + AnnouncementType.TODAY: "TODAY", + AnnouncementType.TOMORROW: "TOMORROW", + AnnouncementType.HOUR: "IN 1 HOUR", + AnnouncementType.B_TIME: "NOW", } - def announcer(time_before): + def announcer(time_delta): - events = get_events_in_time_delta(time_before) + events = get_events_in_time_delta(time_delta) if events: - msg = f"**What's happening {times_msg[time_before]}**" + msg = f"**What's happening {times_msg[time_delta]}**" asyncio.run_coroutine_threadsafe( self.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send(msg), self.loop, ).result(TIMEOUT_SECS) - print("hey hey hey time to check") # debugging + print("Sending: ", time_delta) # debugging send_embed(events) @@ -242,7 +237,8 @@ def send_embed(events): name="Starts", value=event.get_start_date_and_time_string() ) embed.add_field(name="Ends", value=event.get_end_date_and_time_string()) - embed.add_field(name="Link", value=event.link, inline=False) + if event.link: + embed.add_field(name="Link", value=event.link, inline=False) asyncio.run_coroutine_threadsafe( self.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send( embed=embed @@ -250,18 +246,24 @@ def send_embed(events): self.loop, ).result(TIMEOUT_SECS) - # schedule.every().sunday.at("17:00").do(partial(announcer, WEEK)) - # schedule.every().day.at("08:00").do(partial(announcer, TOMORROW)) - # schedule.every().day.at("08:00").do(partial(announcer, TODAY)) - # schedule.every().hour.do(partial(announcer, HOUR)) - # schedule.every(10).minutes.do(partial(announcer, NOW)) - - # For debugging - # schedule.every(10).seconds.do(partial(announcer, "week")) - schedule.every(10).seconds.do(partial(announcer, TOMORROW)) - schedule.every(10).seconds.do(partial(announcer, "today")) - schedule.every(10).seconds.do(partial(announcer, "hour")) - schedule.every(10).seconds.do(partial(announcer, "now")) + 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() diff --git a/csua b/csua deleted file mode 100644 index e69de29..0000000 From b725aa8a6a031ed1faa6d6b5e1b7c08214af5b53 Mon Sep 17 00:00:00 2001 From: Roy Huang Date: Thu, 3 Jun 2021 19:28:17 -0700 Subject: [PATCH 11/11] fixed channel ids --- apps/discordbot/bot.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/discordbot/bot.py b/apps/discordbot/bot.py index 98858bb..705c196 100644 --- a/apps/discordbot/bot.py +++ b/apps/discordbot/bot.py @@ -219,7 +219,7 @@ def announcer(time_delta): if events: msg = f"**What's happening {times_msg[time_delta]}**" asyncio.run_coroutine_threadsafe( - self.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send(msg), + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(msg), self.loop, ).result(TIMEOUT_SECS) print("Sending: ", time_delta) # debugging @@ -240,9 +240,7 @@ def send_embed(events): if event.link: embed.add_field(name="Link", value=event.link, inline=False) asyncio.run_coroutine_threadsafe( - self.client.get_channel(ROY_TEST_SERVER_CHANNEL_ID).send( - embed=embed - ), + self.client.get_channel(ANNOUNCEMENTS_CHANNEL_ID).send(embed=embed), self.loop, ).result(TIMEOUT_SECS)