Compare commits

...
Sign in to create a new pull request.

30 commits

Author SHA1 Message Date
647c0266e1 Merge pull request 'Update dependency pytest-asyncio to v0.25.2' (#27) from renovate/pytest-asyncio-0.x into main
All checks were successful
Test, Build and Publish / test (push) Successful in 34s
Test, Build and Publish / build-and-push-images (push) Successful in 46s
Reviewed-on: #27
2025-01-20 20:19:50 +00:00
c8bfa837aa Update dependency pytest-asyncio to v0.25.2
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 38s
2025-01-08 07:00:08 +00:00
20b0ba08cd Merge pull request 'Update dependency coverage to v7.6.10' (#23) from renovate/coverage-7.x into main
All checks were successful
Test, Build and Publish / test (push) Successful in 34s
Test, Build and Publish / build-and-push-images (push) Successful in 42s
Reviewed-on: #23
2025-01-05 23:15:35 +00:00
f07d52bf19 Merge pull request 'Update dependency pytest-asyncio to v0.25.1' (#26) from renovate/pytest-asyncio-0.x into main
Some checks failed
Test, Build and Publish / build-and-push-images (push) Blocked by required conditions
Test, Build and Publish / test (push) Has been cancelled
Reviewed-on: #26
2025-01-05 23:15:17 +00:00
84f18bceec Update dependency pytest-asyncio to v0.25.1
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 39s
2025-01-02 06:00:08 +00:00
e8f5350b8e Update dependency coverage to v7.6.10
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 41s
2024-12-26 17:00:08 +00:00
e0e927fead Merge pull request 'Update dependency coverage to v7.6.3' (#22) from renovate/coverage-7.x into main
All checks were successful
Test, Build and Publish / test (push) Successful in 39s
Test, Build and Publish / build-and-push-images (push) Successful in 1m36s
Reviewed-on: #22
2024-10-15 18:43:30 +01:00
fb4647c4e0 Update dependency coverage to v7.6.3
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m20s
2024-10-13 23:00:08 +00:00
66d3fe3e5c Merge pull request 'Update dependency coverage to v7.6.2' (#21) from renovate/coverage-7.x into main
All checks were successful
Test, Build and Publish / test (push) Successful in 35s
Test, Build and Publish / build-and-push-images (push) Successful in 1m19s
Reviewed-on: #21
2024-10-13 17:02:52 +01:00
77d533fbcb Update dependency coverage to v7.6.2
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 52s
2024-10-09 12:00:08 +00:00
86d0075e5d Merge pull request 'Add a thread message with suggestions' (#18) from suggest-in-threads into main
All checks were successful
Test, Build and Publish / test (push) Successful in 34s
Test, Build and Publish / build-and-push-images (push) Successful in 1m9s
Reviewed-on: #18
2024-09-24 12:22:33 +01:00
10e3dd3b36 Add more random suggestion options
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 34s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m8s
2024-09-24 11:31:37 +01:00
472f67b3f6 Merge pull request 'Add a reminder for innactive threads' (#19) from add-reminder into main
All checks were successful
Test, Build and Publish / test (push) Successful in 33s
Test, Build and Publish / build-and-push-images (push) Successful in 1m10s
Reviewed-on: #19
2024-09-24 11:16:58 +01:00
f7898892e6 Send a reminder to innactive threads
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 34s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m12s
This sends a reminder message to any thread with 1 or less messages

This picks up only active threads too
2024-09-22 16:30:49 +01:00
455f202364 Add placeholder code for getting previous runs 2024-09-22 15:47:28 +01:00
367d12578c Add a thread message with suggestions
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 33s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m8s
2024-09-22 15:14:19 +01:00
dabd0d9b18 Merge pull request 'Update the readme with the new cadence value' (#16) from mdiluz-patch-2 into main
All checks were successful
Test, Build and Publish / test (push) Successful in 34s
Test, Build and Publish / build-and-push-images (push) Successful in 1m19s
Reviewed-on: #16
2024-09-22 14:43:39 +01:00
3b813a63da Merge branch 'main' into mdiluz-patch-2
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 34s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m19s
2024-09-22 14:41:38 +01:00
e8c5ac9939 Merge pull request 'Call the write next datetime function when sending to the channel' (#17) from fix-schedule-send-button into main
All checks were successful
Test, Build and Publish / test (push) Successful in 33s
Test, Build and Publish / build-and-push-images (push) Successful in 1m16s
Reviewed-on: #17
2024-09-22 14:40:35 +01:00
e3d96faaa7 Call the write next datetime function when sending to the channel
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 34s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m10s
2024-09-22 14:38:09 +01:00
6d6f9d25f9 Update the readme with the new cadence value
Some checks failed
Test, Build and Publish / test (pull_request) Failing after 13m0s
Test, Build and Publish / build-and-push-images (pull_request) Has been cancelled
2024-09-22 14:30:27 +01:00
9d3d57ef30 Merge pull request 'Add a weekly cadence to the match schedule' (#15) from issue-9-advanced-cadence into main
All checks were successful
Test, Build and Publish / test (push) Successful in 33s
Test, Build and Publish / build-and-push-images (push) Successful in 1m9s
Reviewed-on: #15
2024-09-22 14:26:39 +01:00
39bb8dbf19 Remove unused timedelta import
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 33s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m10s
2024-09-22 14:23:59 +01:00
7491a4d2f8 Account for the cadence in all the messages
Some checks failed
Test, Build and Publish / test (pull_request) Failing after 23s
Test, Build and Publish / build-and-push-images (pull_request) Has been skipped
2024-09-22 14:20:47 +01:00
2ad7ed2ff2 Fix not returning values from safe_write functions 2024-09-22 14:20:34 +01:00
93590d14b7 Ensure a cadence of 0 does not get used
The user surely meant 1
2024-09-22 12:22:54 +01:00
d83f933f1d Only yield matching tasks if the weekly cadence has been hit
All checks were successful
Test, Build and Publish / test (pull_request) Successful in 35s
Test, Build and Publish / build-and-push-images (pull_request) Successful in 1m12s
2024-09-22 12:20:55 +01:00
e9cccefacb Add cadence argument to the /schedule command
This allows setting the cadence, or changing it. When changing the initial start time will be reset
2024-09-22 12:09:59 +01:00
b86aaf7016 Add cadence values to the matchy tasks
cadence - Run this task every "x" weeks
cadence - Unix seconds timestamp for the start of this cadence
2024-09-22 11:57:44 +01:00
caadca885c Merge pull request 'Remove TODO from readme' (#14) from mdiluz-patch-1 into main
All checks were successful
Test, Build and Publish / test (push) Successful in 34s
Test, Build and Publish / build-and-push-images (push) Successful in 1m15s
Reviewed-on: #14
2024-09-18 15:46:20 +01:00
7 changed files with 203 additions and 49 deletions

View file

@ -17,7 +17,7 @@ Matchy supports a bunch of user, `matcher` and bot owner commands. `/` commands
| /pause | user | Pauses the user for `days: int` days | | /pause | user | Pauses the user for `days: int` days |
| /list | user | Lists the current matchees and scheduled matches | | /list | user | Lists the current matchees and scheduled matches |
| /match | user* | Shares a preview of the matchee groups of size `group_min: int` with the user. *Offers a button to post the match to `matcher` users | | /match | user* | Shares a preview of the matchee groups of size `group_min: int` with the user. *Offers a button to post the match to `matcher` users |
| /schedule | `matcher` | Schedules a match every week with `group_min: int` users on `weekday: int` day and at `hour: int` hour. Can pass `cancel: True` to stop the schedule | | /schedule | `matcher` | Schedules a match every week with `group_min: int` users on `weekday: int` and at `hour: int`, on a weekly `cadence: int`.<br>Can pass `cancel: True` to stop the schedule |
| /cancel | `matcher` | Cancels any scheduled matches in this channel | | /cancel | `matcher` | Cancels any scheduled matches in this channel |
| $sync | bot owner | Syncs bot command data with the discord servers | | $sync | bot owner | Syncs bot command data with the discord servers |
| $close | bot owner | Closes the bot connection so the bot quits safely | | $close | bot owner | Closes the bot connection so the bot quits safely |

View file

@ -92,8 +92,8 @@ class MatcherCog(commands.Cog):
msg += "\n" + strings.paused_matchees(mentions) + "\n" msg += "\n" + strings.paused_matchees(mentions) + "\n"
tasks = state.State.get_channel_match_tasks(interaction.channel.id) tasks = state.State.get_channel_match_tasks(interaction.channel.id)
for (day, hour, min) in tasks: for (day, hour, min, cadence, cadence_start) in tasks:
next_run = util.get_next_datetime(day, hour) next_run = util.get_next_datetime_with_cadence(day, hour, datetime.now(), cadence, cadence_start)
msg += "\n" + strings.scheduled(next_run, min) msg += "\n" + strings.scheduled(next_run, min)
if not msg: if not msg:
@ -110,7 +110,8 @@ class MatcherCog(commands.Cog):
interaction: discord.Interaction, interaction: discord.Interaction,
members_min: int | None = None, members_min: int | None = None,
weekday: int | None = None, weekday: int | None = None,
hour: int | None = None): hour: int | None = None,
cadence: int | None = None):
"""Schedule a match using the input parameters""" """Schedule a match using the input parameters"""
# Set all the defaults # Set all the defaults
@ -120,6 +121,8 @@ class MatcherCog(commands.Cog):
weekday = 0 weekday = 0
if hour is None: if hour is None:
hour = 9 hour = 9
if cadence is None or cadence == 0:
cadence = 1
channel_id = str(interaction.channel.id) channel_id = str(interaction.channel.id)
# Bail if not a matcher # Bail if not a matcher
@ -129,19 +132,19 @@ class MatcherCog(commands.Cog):
return return
# Add the scheduled task and save # Add the scheduled task and save
state.State.set_channel_match_task( (_, _, _, _, cadence_start) = state.State.set_channel_match_task(
channel_id, members_min, weekday, hour) channel_id, members_min, weekday, hour, cadence)
# Let the user know what happened # Let the user know what happened
logger.info("Scheduled new match task in %s with min %s weekday %s hour %s", logger.info("Scheduled new match task in %s with min %s weekday %s hour %s and cadence %s",
channel_id, members_min, weekday, hour) channel_id, members_min, weekday, hour, cadence)
next_run = util.get_next_datetime(weekday, hour) next_run = util.get_next_datetime_with_cadence(weekday, hour, datetime.now(), cadence, cadence_start)
view = discord.ui.View(timeout=None) view = discord.ui.View(timeout=None)
view.add_item(ScheduleButton()) view.add_item(ScheduleButton())
await interaction.response.send_message( await interaction.response.send_message(
strings.scheduled_success(next_run), strings.scheduled_success(next_run, cadence),
ephemeral=True, silent=True, view=view) ephemeral=True, silent=True, view=view)
@app_commands.command(description="Cancel all scheduled matches in this channel") @app_commands.command(description="Cancel all scheduled matches in this channel")
@ -207,15 +210,28 @@ class MatcherCog(commands.Cog):
async def run_hourly_tasks(self): async def run_hourly_tasks(self):
"""Run any hourly tasks we have""" """Run any hourly tasks we have"""
# Send a reminder for anything that will be active in 1 day
for (channel, _) in state.State.get_active_match_tasks(datetime.now() + timedelta(days=1)):
logger.info("Reminding about scheduled task in %s", channel)
msg_channel = self.bot.get_channel(int(channel))
await msg_channel.send(strings.reminder())
# Match groups for anything active right now
for (channel, min) in state.State.get_active_match_tasks(): for (channel, min) in state.State.get_active_match_tasks():
logger.info("Scheduled match task triggered in %s", channel) logger.info("Scheduled match task triggered in %s", channel)
msg_channel = self.bot.get_channel(int(channel)) msg_channel = self.bot.get_channel(int(channel))
await match_groups_in_channel(msg_channel, min) await match_groups_in_channel(msg_channel, min)
for (channel, _) in state.State.get_active_match_tasks(datetime.now() + timedelta(days=1)): # Send a reminder to threads for a match that happened two days ago
logger.info("Reminding about scheduled task in %s", channel) for (channel, _) in state.State.get_active_match_tasks(datetime.now() - timedelta(days=2)):
logger.info("Sending reminders to threads in %s", channel)
msg_channel = self.bot.get_channel(int(channel)) msg_channel = self.bot.get_channel(int(channel))
await msg_channel.send(strings.reminder()) # Find any threads that need
for thread in msg_channel.threads:
# Only regard threads the bot created
# And that have no additional messages
if thread.owner.id == self.bot.user.id and thread.message_count <= 1:
await thread.send(strings.thread_reminder())
# Increment when adjusting the custom_id so we don't confuse old users # Increment when adjusting the custom_id so we don't confuse old users
@ -269,11 +285,14 @@ async def match_groups_in_channel(channel: discord.channel, min: int):
strings.matched_up([m.mention for m in group])) strings.matched_up([m.mention for m in group]))
# Set up a thread for this match if the bot has permissions to do so # Set up a thread for this match if the bot has permissions to do so
if channel.permissions_for(channel.guild.me).create_public_threads: if channel.permissions_for(channel.guild.me).create_public_threads:
await channel.create_thread( thread = await channel.create_thread(
name=strings.thread_title([m.display_name for m in group]), name=strings.thread_title([m.display_name for m in group]),
message=message, message=message,
reason="Creating a matching thread") reason="Creating a matching thread")
# Send a message with a suggested time to the channel
await thread.send(f"{strings.thread_message()} {strings.time_suggestion()}")
# Close off with a message # Close off with a message
await channel.send(strings.matching_done()) await channel.send(strings.matching_done())
# Save the groups to the history # Save the groups to the history
@ -304,8 +323,8 @@ class ScheduleButton(discord.ui.Button):
msg += strings.scheduled_matches() msg += strings.scheduled_matches()
if tasks: if tasks:
for (day, hour, min) in tasks: for (day, hour, min, cadence, cadence_start) in tasks:
next_run = util.get_next_datetime(day, hour) next_run = util.get_next_datetime_with_cadence(day, hour, datetime.now(), cadence, cadence_start)
msg += strings.scheduled(next_run, min) msg += strings.scheduled(next_run, min)
await interaction.channel.send(msg) await interaction.channel.send(msg)

View file

@ -85,11 +85,11 @@ def need_matcher_scope(): return [
@randomised @randomised
def scheduled_success(d): return [ def scheduled_success(d, cadence): return [
f"Done :) Next run will be at {datetime_as_discord_time(d)}", f"Done :) Next run will be at {datetime_as_discord_time(d)} and every {cadence} week(s) after",
f"Woohoo! Scheduled for {datetime_as_discord_time(d)}", f"Woohoo! Scheduled for {datetime_as_discord_time(d)} plus each {cadence} week(s) after",
f"Yessir, will do a matcho at {datetime_as_discord_time(d)}", f"Yessir, will do a matcho every {cadence} week(s), first one is {datetime_as_discord_time(d)}",
f"Arf Arf! Bork bork bark {datetime_as_discord_time(d)}", f"Arf Arf! Bork bork bark {datetime_as_discord_time(d)} berk {cadence} week(s) arf",
] ]
@ -145,6 +145,16 @@ Make sure you're /pause'd if you need to be, or /join in ASAP!""",
] ]
@randomised
def thread_reminder(): return [
"Hey friends, just checking in! No worries if you're too busy this week",
"Bork bork, quick reminder in case y'all forgot!",
"Hey matchees, how's your week going?",
"Hey everyone, don't forget to check in with eachother!",
"Quick friendly nudge, how're you all doing?",
]
@randomised @randomised
def matching(): return [ def matching(): return [
"Matchy is matching matchees...", "Matchy is matching matchees...",
@ -174,6 +184,36 @@ def matched_up(ms): return [
] ]
@randomised
def thread_message(): return [
"Hey peeps :)",
"How is everyone?",
"Bork!",
"Hey kiddos :)",
"Ahoy!",
"Great to see y'all here.",
"Icebreaker! What's your favourite pokemon?",
"I'm hungry, would a lasagna count as a sandwich?",
"What's your favourite keyboard key?",
"I'm confused thinking... Is a train just a sideways elevator?",
"Humans are weird, why do you have moustaches above your eyes?"
]
@randomised
def time_suggestion(): return [
"Can I suggest a quick call on Wednesday?",
"Remember to organise a chat if you're up for it!",
"How about throwing something in the calendar for Friday?",
"Would 10am on Thursday work for people?",
"How about a call this afternoon?",
"Would a chat the start of a weekday work?",
"How's about organising a quick call sometime this week?",
"When's everyone available for a short hangout?",
"It's best to pick a time to drop in a call or meet up, if everyone is up for it!",
]
@randomised @randomised
def thread_title(ms): return [ def thread_title(ms): return [
f"{format_list(ms)}", f"{format_list(ms)}",

View file

@ -16,7 +16,7 @@ logger = logging.getLogger("state")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# Warning: Changing any of the below needs proper thought to ensure backwards compatibility # Warning: Changing any of the below needs proper thought to ensure backwards compatibility
_VERSION = 4 _VERSION = 5
def _migrate_to_v1(d: dict): def _migrate_to_v1(d: dict):
@ -64,12 +64,24 @@ def _migrate_to_v4(d: dict):
del d[_Key._HISTORY] del d[_Key._HISTORY]
def _migrate_to_v5(d: dict):
"""v5 added weekly cadence"""
tasks = d.get(_Key.TASKS, {})
for tasks in tasks.values():
match_tasks = tasks.get(_Key.MATCH_TASKS, [])
for match in match_tasks:
# All previous matches were every week starting from now
match[_Key.CADENCE] = 1
match[_Key.CADENCE_START] = datetime_to_ts(datetime.now())
# Set of migration functions to apply # Set of migration functions to apply
_MIGRATIONS = [ _MIGRATIONS = [
_migrate_to_v1, _migrate_to_v1,
_migrate_to_v2, _migrate_to_v2,
_migrate_to_v3, _migrate_to_v3,
_migrate_to_v4, _migrate_to_v4,
_migrate_to_v5
] ]
@ -94,6 +106,8 @@ class _Key(str):
MEMBERS_MIN = "members_min" MEMBERS_MIN = "members_min"
WEEKDAY = "weekdays" WEEKDAY = "weekdays"
HOUR = "hours" HOUR = "hours"
CADENCE = "cadence"
CADENCE_START = "cadence_start"
# Unused # Unused
_MATCHEES = "matchees" _MATCHEES = "matchees"
@ -139,6 +153,8 @@ _SCHEMA = Schema(
_Key.MEMBERS_MIN: Use(int), _Key.MEMBERS_MIN: Use(int),
_Key.WEEKDAY: Use(int), _Key.WEEKDAY: Use(int),
_Key.HOUR: Use(int), _Key.HOUR: Use(int),
_Key.CADENCE: Use(int),
_Key.CADENCE_START: Use(str),
} }
] ]
} }
@ -216,11 +232,12 @@ class _State():
@wraps(func) @wraps(func)
def inner(self, *args, **kwargs): def inner(self, *args, **kwargs):
tmp = _State(self._dict, self._file) tmp = _State(self._dict, self._file)
func(tmp, *args, **kwargs) ret = func(tmp, *args, **kwargs)
_SCHEMA.validate(tmp._dict) _SCHEMA.validate(tmp._dict)
if tmp._file: if tmp._file:
_save(tmp._file, tmp._dict) _save(tmp._file, tmp._dict)
self._dict = tmp._dict self._dict = tmp._dict
return ret
return inner return inner
@ -324,7 +341,10 @@ class _State():
for channel, tasks in self._tasks.items(): for channel, tasks in self._tasks.items():
for match in tasks.get(_Key.MATCH_TASKS, []): for match in tasks.get(_Key.MATCH_TASKS, []):
if match[_Key.WEEKDAY] == weekday and match[_Key.HOUR] == hour: # Take into account the weekly cadence
start = ts_to_datetime(match[_Key.CADENCE_START])
weeks = int((time - start).days / 7)
if match[_Key.WEEKDAY] == weekday and match[_Key.HOUR] == hour and weeks % match[_Key.CADENCE] == 0:
yield (channel, match[_Key.MEMBERS_MIN]) yield (channel, match[_Key.MEMBERS_MIN])
def get_channel_match_tasks(self, channel_id: str) -> Generator[int, int, int]: def get_channel_match_tasks(self, channel_id: str) -> Generator[int, int, int]:
@ -338,30 +358,35 @@ class _State():
) )
for tasks in all_tasks: for tasks in all_tasks:
for task in tasks: for task in tasks:
yield (task[_Key.WEEKDAY], task[_Key.HOUR], task[_Key.MEMBERS_MIN]) yield _task_to_tuple(task)
@safe_write @safe_write
def set_channel_match_task(self, channel_id: str, members_min: int, weekday: int, hour: int): def set_channel_match_task(self, channel_id: str, members_min: int, weekday: int, hour: int, cadence: int):
"""Set up a match task on a channel""" """Set up a match task on a channel"""
channel = self._tasks.setdefault(str(channel_id), {}) channel = self._tasks.setdefault(str(channel_id), {})
matches = channel.setdefault(_Key.MATCH_TASKS, []) matches = channel.setdefault(_Key.MATCH_TASKS, [])
found = False for match_task in matches:
for match in matches:
# Specifically check for the combination of weekday and hour # Specifically check for the combination of weekday and hour
if match[_Key.WEEKDAY] == weekday and match[_Key.HOUR] == hour: if match_task[_Key.WEEKDAY] == weekday and match_task[_Key.HOUR] == hour:
found = True match_task[_Key.MEMBERS_MIN] = members_min
match[_Key.MEMBERS_MIN] = members_min # If the cadence has changed, update it and reset the start
# Return true as we've successfully changed the data in place if cadence != match_task[_Key.CADENCE]:
return True match_task[_Key.CADENCE] = cadence
match_task[_Key.CADENCE_START] = datetime_to_ts(datetime.now())
# Return as we've successfully changed the data in place
return _task_to_tuple(match_task)
# If we didn't find it, add it to the schedule # If we didn't find it, add it to the schedule
if not found: match_task = {
matches.append({ _Key.MEMBERS_MIN: members_min,
_Key.MEMBERS_MIN: members_min, _Key.WEEKDAY: weekday,
_Key.WEEKDAY: weekday, _Key.HOUR: hour,
_Key.HOUR: hour, _Key.CADENCE: cadence,
}) _Key.CADENCE_START: datetime_to_ts(datetime.now())
}
matches.append(match_task)
return _task_to_tuple(match_task)
@safe_write @safe_write
def remove_channel_match_tasks(self, channel_id: str): def remove_channel_match_tasks(self, channel_id: str):
@ -379,6 +404,14 @@ class _State():
return self._dict[_Key.TASKS] return self._dict[_Key.TASKS]
def _task_to_tuple(task):
return (task[_Key.WEEKDAY],
task[_Key.HOUR],
task[_Key.MEMBERS_MIN],
task[_Key.CADENCE],
ts_to_datetime(task[_Key.CADENCE_START]))
def load_from_file(file: str) -> _State: def load_from_file(file: str) -> _State:
""" """
Load the state from a files Load the state from a files

View file

@ -18,20 +18,32 @@ def format_list(list: list) -> str:
return list[0] if list else '' return list[0] if list else ''
def get_next_datetime(weekday, hour) -> datetime: def get_next_datetime(weekday: int, hour: int, start: datetime) -> datetime:
"""Get the next datetime for the given weekday and hour""" """Get the next datetime for the given weekday and hour"""
now = datetime.now()
days_until_next_week = (weekday - now.weekday() + 7) % 7 days_until_next_week_run = (weekday - start.weekday() + 7) % 7
# Account for when we're already beyond the time now # Account for when we're already beyond the time now
if days_until_next_week == 0 and now.hour >= hour: if days_until_next_week_run == 0 and start.hour >= hour:
days_until_next_week = 7 days_until_next_week_run = 7
# Calculate the next datetime # Calculate the next datetime
next_date = now + timedelta(days=days_until_next_week) next_date = start + timedelta(days=days_until_next_week_run)
next_date = next_date.replace(hour=hour, minute=0, second=0, microsecond=0)
return next_date return next_date.replace(hour=hour, minute=0, second=0, microsecond=0)
def get_next_datetime_with_cadence(weekday: int, hour: int, start: datetime, cadence: int, cadence_start: datetime):
"""Get the next datetime for given weekday, hour and cadence values"""
# Get the first run based on the cadence
next_time = get_next_datetime(weekday, hour, cadence_start)
# Walk forwards until we get the actual time
while next_time < start:
next_time += timedelta(weeks=cadence)
return next_time
def datetime_as_discord_time(time: datetime) -> str: def datetime_as_discord_time(time: datetime) -> str:

View file

@ -1,8 +1,8 @@
coverage==7.6.1 coverage==7.6.10
discord.py==2.4.0 discord.py==2.4.0
dpytest==0.7.0 dpytest==0.7.0
flake8==7.1.1 flake8==7.1.1
pytest==8.3.3 pytest==8.3.3
pytest-asyncio==0.24.0 pytest-asyncio==0.25.2
pytest-cov==5.0.0 pytest-cov==5.0.0
schema==0.7.7 schema==0.7.7

View file

@ -1,4 +1,6 @@
import matchy.util as util import matchy.util as util
from datetime import datetime
import pytest
def test_iterate_all_shifts(): def test_iterate_all_shifts():
@ -50,3 +52,51 @@ def test_randomized():
assert util.randomised(string)() == "foo" assert util.randomised(string)() == "foo"
assert util.randomised(list)() in list() assert util.randomised(list)() in list()
@pytest.mark.parametrize(
"weekday, hour, start, expected",
[
pytest.param(
0, 0, datetime(2024, 9, 22),
datetime(2024, 9, 23), id="tomorrow"
),
pytest.param(
4, 16, datetime(2024, 9, 22),
datetime(2024, 9, 27, 16), id="complicated"
),
],
)
def test_get_next_datetime(weekday, hour, start, expected):
value = util.get_next_datetime(weekday, hour, start)
assert value == expected
@pytest.mark.parametrize(
"weekday, hour, start, cadence, cadence_start, expected",
[
pytest.param(
0, 0, datetime(2024, 9, 22),
1, datetime(2024, 9, 22),
datetime(2024, 9, 23), id="tomorrow"
),
pytest.param(
0, 0, datetime(2024, 9, 22),
2, datetime(2024, 9, 22),
datetime(2024, 9, 23), id="every-other"
),
pytest.param(
0, 0, datetime(2024, 9, 22),
2, datetime(2024, 9, 14),
datetime(2024, 9, 30), id="every-other-before"
),
pytest.param(
0, 0, datetime(2024, 9, 22),
3, datetime(2024, 9, 14),
datetime(2024, 10, 7), id="every-third"
),
],
)
def test_get_next_datetime_with_cadence(weekday, hour, start, expected, cadence, cadence_start):
value = util.get_next_datetime_with_cadence(weekday, hour, start, cadence, cadence_start)
assert value == expected