Skip to content

Conversation

@Aqua-218
Copy link

Overview

スケジュール投稿機能の追加

Issue

#214

@Aqua-218 Aqua-218 requested a review from a team as a code owner January 27, 2026 15:33
Copilot AI review requested due to automatic review settings January 27, 2026 15:33
Copy link
Contributor

Copilot AI left a 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 due ScheduleSetting records and posts messages, updating or deleting schedules as appropriate.
  • Introduce a /schedule command with set, list, and remove subcommands, 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.

Comment on lines +117 to +132
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")
Copy link

Copilot AI Jan 27, 2026

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).

Suggested change
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")

Copilot uses AI. Check for mistakes.
@yuito-it yuito-it added kind/feature 新機能のリクエスト scope/schedule 予約投稿に関するIssue labels Jan 27, 2026
@yuito-it yuito-it moved this from Untouched to In Progress in UniBot Feature Relase Jan 27, 2026
@yuito-it yuito-it moved this from In Progress to In Review in UniBot Feature Relase Jan 27, 2026
@yuito-it yuito-it changed the title スケジュール投稿機能の追加 [v9] スケジュール投稿機能の追加 Jan 28, 2026
@yuito-it yuito-it added this to the v9 - Go Edition milestone Jan 28, 2026
@yuito-it yuito-it linked an issue Jan 28, 2026 that may be closed by this pull request
3 tasks
Copy link
Member

@yuito-it yuito-it left a 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')

@github-project-automation github-project-automation bot moved this from In Review to In Progress in UniBot Feature Relase Jan 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature 新機能のリクエスト scope/schedule 予約投稿に関するIssue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v9] スケジュール投稿

3 participants