-
Notifications
You must be signed in to change notification settings - Fork 0
[v9] スケジュール投稿機能の追加 #225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/go
Are you sure you want to change the base?
[v9] スケジュール投稿機能の追加 #225
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a schedule posting feature: users can create one-time or recurring scheduled messages via slash commands, which are persisted and executed by a background scheduler.
Changes:
- Add a background scheduler (
internal/scheduler.Manager) that periodically polls dueScheduleSettingrecords and posts messages, updating or deleting schedules as appropriate. - Introduce a
/schedulecommand withset,list, andremovesubcommands, including modal-based UX for one-time and recurring schedules and permission checks for authorized use. - Implement natural-language recurring schedule parsing and conversion to cron expressions, plus repository and wiring changes to integrate scheduling into the bot startup and interaction handling.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/internal/scheduler/manager.go | Adds a scheduler manager that ticks every 30 seconds, finds due schedules, sends messages, and advances or deletes them based on cron settings. |
| src/internal/repository/schedule_setting.go | Extends the repository with ListDue to fetch schedules whose next_run_at is due or past. |
| src/internal/bot/handler/interaction.go | Wires InteractionModalSubmit handling to the schedule module’s modal handler. |
| src/internal/bot/command/registry.go | Registers the new schedule top-level command handler. |
| src/internal/bot/command/general/schedule/set.go | Implements the schedule set subcommand and presents modals for one-time or repeating schedules. |
| src/internal/bot/command/general/schedule/remove.go | Implements the schedule remove subcommand for deleting a scheduled job by ID with basic validation and guild ownership checks. |
| src/internal/bot/command/general/schedule/list.go | Implements the schedule list subcommand that shows scheduled jobs for the current channel in an ephemeral embed. |
| src/internal/bot/command/general/schedule/modals.go | Handles modal submissions for creating one-time and repeating schedules, including validation, permission checks, persistence, and success/error responses. |
| src/internal/bot/command/general/schedule/cron.go | Adds natural-language schedule parsing and conversion to cron, plus user-facing descriptions of cron expressions. |
| src/internal/bot/command/general/schedule.go | Defines the /schedule command schema, dispatches to set/list/remove, and enforces guild and permission checks. |
| src/internal/bot/command/commands.go | Registers LoadScheduleCommandContext so the /schedule slash command is created at startup. |
| src/go.mod | Adds github.com/robfig/cron/v3 as an indirect dependency for cron parsing. |
| src/go.sum | Records checksums for the newly added robfig/cron/v3 dependency. |
| src/cmd/bot/main.go | Instantiates and starts the scheduler manager alongside the Discord bot lifecycle. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| switch { | ||
| case diffMinutes < 60: | ||
| return fmt.Sprintf("*/%d * * * *", diffMinutes), nil | ||
| case diffMinutes%60 == 0 && diffMinutes < 1440: | ||
| hours := diffMinutes / 60 | ||
| return fmt.Sprintf("%d */%d * * *", min, hours), nil | ||
| case diffMinutes >= 1440 && diffMinutes < 10080: | ||
| return fmt.Sprintf("%d %d * * *", min, hour), nil | ||
| case diffMinutes >= 10080 && diffMinutes < 40320: | ||
| return fmt.Sprintf("%d %d * * %d", min, hour, weekDay), nil | ||
| case diffMinutes >= 40320 && diffMinutes < 525600: | ||
| return fmt.Sprintf("%d %d %d * *", min, hour, date), nil | ||
| case diffMinutes >= 525600: | ||
| return fmt.Sprintf("%d %d %d %d *", min, hour, date, month), nil | ||
| default: | ||
| return "", errors.New("invalid schedule") |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
convertToCron currently allows free-form intervals like every 3 days / every 2 weeks via parseScheduleText, but the switch on diffMinutes collapses many of these into simpler crons (e.g. every 3 days becomes a daily schedule, every 2 weeks becomes weekly), so the actual execution interval no longer matches the user input. This can lead to surprising behavior where a user thinks they scheduled a message for a multi-day/weekly interval, but it fires more frequently than requested. To avoid this, either reject intervals that cannot be represented exactly in a 5-field cron (returning a validation error instead of a best-effort cron), or extend the cron expression logic so that multi-day/week/month intervals are represented faithfully (for example using */N-style fields where appropriate or a different scheduling mechanism).
| switch { | |
| case diffMinutes < 60: | |
| return fmt.Sprintf("*/%d * * * *", diffMinutes), nil | |
| case diffMinutes%60 == 0 && diffMinutes < 1440: | |
| hours := diffMinutes / 60 | |
| return fmt.Sprintf("%d */%d * * *", min, hours), nil | |
| case diffMinutes >= 1440 && diffMinutes < 10080: | |
| return fmt.Sprintf("%d %d * * *", min, hour), nil | |
| case diffMinutes >= 10080 && diffMinutes < 40320: | |
| return fmt.Sprintf("%d %d * * %d", min, hour, weekDay), nil | |
| case diffMinutes >= 40320 && diffMinutes < 525600: | |
| return fmt.Sprintf("%d %d %d * *", min, hour, date), nil | |
| case diffMinutes >= 525600: | |
| return fmt.Sprintf("%d %d %d %d *", min, hour, date, month), nil | |
| default: | |
| return "", errors.New("invalid schedule") | |
| const ( | |
| dayMinutes = 24 * 60 | |
| weekMinutes = 7 * dayMinutes | |
| yearMinutes = 365 * dayMinutes | |
| ) | |
| switch { | |
| // Sub-hour intervals: every N minutes | |
| case diffMinutes < 60: | |
| return fmt.Sprintf("*/%d * * * *", diffMinutes), nil | |
| // Sub-day intervals that are an exact number of hours: every N hours | |
| case diffMinutes%60 == 0 && diffMinutes < dayMinutes: | |
| hours := diffMinutes / 60 | |
| return fmt.Sprintf("%d */%d * * *", min, hours), nil | |
| // Exactly once per day | |
| case diffMinutes == dayMinutes: | |
| return fmt.Sprintf("%d %d * * *", min, hour), nil | |
| // Exactly once per week | |
| case diffMinutes == weekMinutes: | |
| return fmt.Sprintf("%d %d * * %d", min, hour, weekDay), nil | |
| // Monthly-ish schedules (e.g. "every month") – allow any interval >= 28 days and < 1 year | |
| case diffMinutes >= 28*dayMinutes && diffMinutes < yearMinutes: | |
| return fmt.Sprintf("%d %d %d * *", min, hour, date), nil | |
| // Yearly or longer intervals | |
| case diffMinutes >= yearMinutes: | |
| return fmt.Sprintf("%d %d %d %d *", min, hour, date, month), nil | |
| default: | |
| // Intervals such as "every 3 days" or "every 2 weeks" cannot be represented | |
| // faithfully with a standard 5-field cron expression, so reject them. | |
| return "", errors.New("schedule interval cannot be represented exactly as a cron expression") |
yuito-it
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
リレーションシップの部分でエラーが発生しています。
また、全ての応答は埋め込みで返却してください。
2026/01/29 19:45:17 /Users/yutaka/Documents/Dev/UniProject/UniBot/src/internal/repository/schedule_setting.go:24 ERROR: insert or update on table "schedule_settings" violates foreign key constraint "fk_members_schedule_setting" (SQLSTATE 23503)
[21.679ms] [rows:0] INSERT INTO "schedule_settings" ("id","channel_id","content","next_run_at","cron","created_at","updated_at","guild_id","author_id") VALUES ('1466383696948105381','1383480147080118345','しばいぬさんはかわいい',1769687160,'',1769683517575446000,1769683517575446000,'1383480145486286879','957110410891391006')
1fac375 to
bab9f7f
Compare
Overview
スケジュール投稿機能の追加
Issue
#214