|
@@ -0,0 +1,151 @@
|
|
|
+from typing import Optional
|
|
|
+import datetime
|
|
|
+
|
|
|
+from rollbot import as_command, initialize_data, RollbotFailure
|
|
|
+from rollbot.injection import Subcommand, Data, ChannelAdmin, Channel, Lazy
|
|
|
+
|
|
|
+
|
|
|
+@initialize_data
|
|
|
+class DnDSession:
|
|
|
+ session_time: Optional[float] = None
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def fmt(sesh):
|
|
|
+ if (
|
|
|
+ sesh is None
|
|
|
+ or sesh.session_time is None
|
|
|
+ or sesh.session_time < datetime.datetime.now().timestamp()
|
|
|
+ ):
|
|
|
+ return "No upcoming session found."
|
|
|
+ return "Next DnD session scheduled for " + datetime.datetime.fromtimestamp(
|
|
|
+ sesh.session_time
|
|
|
+ ).strftime("%b %d, %Y, at %I:%M %p")
|
|
|
+
|
|
|
+
|
|
|
+WEEKDAYS = {
|
|
|
+ "monday": 0,
|
|
|
+ "tuesday": 1,
|
|
|
+ "wednesday": 2,
|
|
|
+ "thursday": 3,
|
|
|
+ "friday": 4,
|
|
|
+ "saturday": 5,
|
|
|
+ "sunday": 6,
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+def pop_arg(text):
|
|
|
+ if text is None:
|
|
|
+ return None, None
|
|
|
+ text = text.strip()
|
|
|
+ parts = text.split(maxsplit=1)
|
|
|
+ if len(parts) == 1:
|
|
|
+ return parts[0], None
|
|
|
+ return parts[0], parts[1]
|
|
|
+
|
|
|
+
|
|
|
+def parse_datetime(arg_text):
|
|
|
+ if arg_text is None:
|
|
|
+ return
|
|
|
+
|
|
|
+ day_date, arg_text = pop_arg(arg_text)
|
|
|
+ day_date = day_date.lower()
|
|
|
+ if day_date in WEEKDAYS:
|
|
|
+ today = datetime.date.today()
|
|
|
+ day_diff = WEEKDAYS[day_date] - today.weekday()
|
|
|
+ next_day = today + datetime.timedelta((day_diff) % 7)
|
|
|
+ month = next_day.month
|
|
|
+ day = next_day.day
|
|
|
+ year = next_day.year
|
|
|
+ else:
|
|
|
+ date_parts = day_date.split("/")
|
|
|
+ if len(date_parts) == 3:
|
|
|
+ month = date_parts[0]
|
|
|
+ day = date_parts[1]
|
|
|
+ year = date_parts[2]
|
|
|
+ elif len(date_parts) == 2:
|
|
|
+ month = date_parts[0]
|
|
|
+ day = date_parts[1]
|
|
|
+ year = datetime.date.today().year
|
|
|
+ else:
|
|
|
+ return
|
|
|
+
|
|
|
+ hour = 19
|
|
|
+ minute = 45
|
|
|
+
|
|
|
+ if arg_text is None:
|
|
|
+ return datetime.datetime(int(year), int(month), int(day), int(hour), int(minute))
|
|
|
+
|
|
|
+ time, arg_text = pop_arg(arg_text)
|
|
|
+ time_parts = time.split(":")
|
|
|
+ hour = time_parts[0]
|
|
|
+ if len(time_parts) > 1:
|
|
|
+ minute = time_parts[1]
|
|
|
+
|
|
|
+ ampm, arg_text = pop_arg(arg_text)
|
|
|
+
|
|
|
+ if ampm is None:
|
|
|
+ return datetime.datetime(int(year), int(month), int(day), int(hour), int(minute))
|
|
|
+
|
|
|
+ if ampm.lower() == "pm":
|
|
|
+ hour = int(hour) + 12
|
|
|
+
|
|
|
+ return datetime.datetime(int(year), int(month), int(day), int(hour), int(minute))
|
|
|
+
|
|
|
+
|
|
|
+SUBCOMMANDS = ("next", "view", "late", "cancel")
|
|
|
+VALID_DETAIL = "Valid subcommands are: next, view, late, and cancel"
|
|
|
+NEEDS_ADMIN = ("next", "cancel")
|
|
|
+
|
|
|
+
|
|
|
+@as_command
|
|
|
+async def session(
|
|
|
+ subc: Subcommand,
|
|
|
+ get_sesh: Lazy(Data(DnDSession).For(Channel)),
|
|
|
+ store: Data(DnDSession),
|
|
|
+ admin: ChannelAdmin,
|
|
|
+ channel: Channel,
|
|
|
+):
|
|
|
+ if subc is None:
|
|
|
+ RollbotFailure.MISSING_SUBCOMMAND.raise_exc(detail=VALID_DETAIL)
|
|
|
+
|
|
|
+ if subc.name not in SUBCOMMANDS:
|
|
|
+ RollbotFailure.INVALID_SUBCOMMAND.raise_exc(detail=VALID_DETAIL)
|
|
|
+
|
|
|
+ if not admin and subc.name in NEEDS_ADMIN:
|
|
|
+ RollbotFailure.PERMISSIONS.raise_exc()
|
|
|
+
|
|
|
+ sesh = await get_sesh()
|
|
|
+
|
|
|
+ if subc.name == "view":
|
|
|
+ return DnDSession.fmt(sesh)
|
|
|
+
|
|
|
+ if subc.name == "next":
|
|
|
+ dt = parse_datetime(subc.args)
|
|
|
+ if dt is None:
|
|
|
+ RollbotFailure.INVALID_ARGUMENTS.raise_exc(
|
|
|
+ detail="Could not process argument as a date/time."
|
|
|
+ )
|
|
|
+ if dt < datetime.datetime.now():
|
|
|
+ RollbotFailure.INVALID_ARGUMENTS.raise_exc(
|
|
|
+ detail="Cannot schedule sessions in the past."
|
|
|
+ )
|
|
|
+
|
|
|
+ sesh.session_time = dt.timestamp()
|
|
|
+ await store.save(channel, sesh)
|
|
|
+ return DnDSession.fmt(sesh)
|
|
|
+
|
|
|
+ if sesh is None or sesh.session_time is None:
|
|
|
+ return "No upcoming session found."
|
|
|
+
|
|
|
+ if subc.name == "late":
|
|
|
+ now = datetime.datetime.now().timestamp()
|
|
|
+ if sesh.session_time >= now:
|
|
|
+ return "You're not late yet!"
|
|
|
+
|
|
|
+ late = (now - sesh.session_time) / 60
|
|
|
+ return f"You're running {late:.2f} minutes late..."
|
|
|
+
|
|
|
+ if subc.name == "cancel":
|
|
|
+ sesh.session_time = None
|
|
|
+ await store.save(channel, sesh)
|
|
|
+ return "Sigh, session cancelled..."
|