feat: schedule rework
This commit is contained in:
@@ -0,0 +1,794 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"velox-bot/internal/commands/schedule/shared"
|
||||
"velox-bot/internal/db/repos/schedulerepo"
|
||||
"velox-bot/internal/db/services"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
func scheduleActionButtons(scheduleID int64) []discordgo.MessageComponent {
|
||||
|
||||
idStr := strconv.FormatInt(scheduleID, 10)
|
||||
|
||||
return []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.Button{
|
||||
Label: "Accept",
|
||||
Style: discordgo.SuccessButton,
|
||||
CustomID: "schedule:accept:" + idStr,
|
||||
},
|
||||
discordgo.Button{
|
||||
Label: "Decline",
|
||||
Style: discordgo.DangerButton,
|
||||
CustomID: "schedule:decline:" + idStr,
|
||||
},
|
||||
discordgo.Button{
|
||||
Label: "Reschedule",
|
||||
Style: discordgo.SecondaryButton,
|
||||
CustomID: "schedule:reschedule:" + idStr,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func rescheduleProposalButtons(scheduleID int64) []discordgo.MessageComponent {
|
||||
idStr := strconv.FormatInt(scheduleID, 10)
|
||||
|
||||
return []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.Button{
|
||||
Label: "Accept",
|
||||
Style: discordgo.SuccessButton,
|
||||
CustomID: "schedule:accept_reschedule:" + idStr,
|
||||
},
|
||||
discordgo.Button{
|
||||
Label: "Decline",
|
||||
Style: discordgo.DangerButton,
|
||||
CustomID: "schedule:decline_reschedule:" + idStr,
|
||||
},
|
||||
discordgo.Button{
|
||||
Label: "Propose different time",
|
||||
Style: discordgo.SecondaryButton,
|
||||
CustomID: "schedule:reschedule:" + idStr,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const createUserSelectCustomID = "schedule:create_user_select"
|
||||
|
||||
func HandleComponent(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
if i.MessageComponentData().CustomID == createUserSelectCustomID {
|
||||
handleCreateUserSelect(s, i)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
action, scheduleID := parseComponentCustomID(i.MessageComponentData().CustomID)
|
||||
|
||||
userIDStr := interactionUserID(i)
|
||||
userID, err := strconv.ParseInt(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error parsing user ID.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sch, err := services.Global.Schedule.GetByID(ctx, scheduleID)
|
||||
if err != nil || sch == nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error retrieving schedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var status schedulerepo.ScheduleStatus
|
||||
var message string
|
||||
|
||||
switch action {
|
||||
case "accept":
|
||||
if userID != sch.InviteeID {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if sch.Status != schedulerepo.StatusPending {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "This schedule is no longer pending.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status = schedulerepo.StatusAccepted
|
||||
message = "✅ Accepted"
|
||||
case "decline":
|
||||
if userID != sch.InviteeID {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if sch.Status != schedulerepo.StatusPending {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "This schedule is no longer pending.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
status = schedulerepo.StatusDeclined
|
||||
message = "❌ Declined"
|
||||
case "reschedule":
|
||||
if userID != sch.InviteeID && userID != sch.RequesterID {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if sch.Status != schedulerepo.StatusPending && sch.Status != schedulerepo.StatusAccepted {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "This schedule can't be rescheduled right now.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseModal,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
CustomID: "schedule:reschedule_modal:" + strconv.FormatInt(scheduleID, 10),
|
||||
Title: "Propose a new time",
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "date",
|
||||
Label: "Date (YYYY-MM-DD)",
|
||||
Style: discordgo.TextInputShort,
|
||||
Required: true,
|
||||
Placeholder: "2026-01-02",
|
||||
},
|
||||
},
|
||||
},
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "time",
|
||||
Label: "Time (HH:MM, your timezone)",
|
||||
Style: discordgo.TextInputShort,
|
||||
Required: true,
|
||||
Placeholder: "15:04",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
case "accept_reschedule":
|
||||
if sch.ProposedBy == nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "There is no reschedule proposal to accept.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (userID != sch.RequesterID && userID != sch.InviteeID) || userID == *sch.ProposedBy {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if sch.Status != schedulerepo.StatusRescheduleRequested {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "There is no reschedule request to accept.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = services.Global.Schedule.AcceptReschedule(ctx, scheduleID)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error accepting reschedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseUpdateMessage,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "✅ Reschedule accepted",
|
||||
Components: []discordgo.MessageComponent{},
|
||||
},
|
||||
})
|
||||
|
||||
newTime := *sch.ProposedAt
|
||||
utcStr := newTime.UTC().Format("2006-01-02 15:04")
|
||||
|
||||
loc, zone, _, err := services.Global.UserSettings.GetTimezone(ctx, *sch.ProposedBy)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
zone = "UTC"
|
||||
}
|
||||
localStr := newTime.In(loc).Format("2006-01-02 15:04")
|
||||
|
||||
content := "Your proposed time for the session was accepted: " +
|
||||
utcStr + " UTC (" + localStr + " " + zone + ")."
|
||||
|
||||
dmCh, err := s.UserChannelCreate(strconv.FormatInt(*sch.ProposedBy, 10))
|
||||
if err == nil {
|
||||
if _, err := s.ChannelMessageSend(dmCh.ID, content); err != nil {
|
||||
log.Printf("schedule: failed to DM proposer about accepted reschedule: %v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
case "decline_reschedule":
|
||||
if sch.ProposedBy == nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "There is no reschedule proposal to accept.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (userID != sch.RequesterID && userID != sch.InviteeID) || userID == *sch.ProposedBy {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if sch.Status != schedulerepo.StatusRescheduleRequested {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "There is no reschedule request to accept.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = services.Global.Schedule.DeclineReschedule(ctx, scheduleID)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error declining reschedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseUpdateMessage,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "❌ Reschedule declined",
|
||||
Components: []discordgo.MessageComponent{},
|
||||
},
|
||||
})
|
||||
|
||||
utcStr := sch.ScheduledAt.UTC().Format("2006-01-02 15:04")
|
||||
|
||||
loc, zone, _, err := services.Global.UserSettings.GetTimezone(ctx, *sch.ProposedBy)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
zone = "UTC"
|
||||
}
|
||||
localStr := sch.ScheduledAt.In(loc).Format("2006-01-02 15:04")
|
||||
|
||||
content := "Your proposed new time was declined. The session remains scheduled for " +
|
||||
utcStr + " UTC (" + localStr + " " + zone + ")."
|
||||
|
||||
dmCh, err := s.UserChannelCreate(strconv.FormatInt(*sch.ProposedBy, 10))
|
||||
if err == nil {
|
||||
if _, err := s.ChannelMessageSend(dmCh.ID, content); err != nil {
|
||||
log.Printf("schedule: failed to DM proposer about declined reschedule: %v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Unknown action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = services.Global.Schedule.UpdateStatus(ctx, scheduleID, status)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error updating schedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseUpdateMessage,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: message,
|
||||
Components: []discordgo.MessageComponent{},
|
||||
},
|
||||
})
|
||||
|
||||
statusText := map[schedulerepo.ScheduleStatus]string{
|
||||
schedulerepo.StatusAccepted: "accepted ✅",
|
||||
schedulerepo.StatusDeclined: "declined ❌",
|
||||
schedulerepo.StatusRescheduleRequested: "requested rescheduling 🔁",
|
||||
}[status]
|
||||
|
||||
utcStr := sch.ScheduledAt.UTC().Format("2006-01-02 15:04")
|
||||
|
||||
// Notify requester with local time if configured.
|
||||
if services.Global.UserSettings != nil {
|
||||
loc, zone, _, err := services.Global.UserSettings.GetTimezone(ctx, sch.RequesterID)
|
||||
if err != nil {
|
||||
loc = sch.ScheduledAt.UTC().Location()
|
||||
zone = "UTC"
|
||||
}
|
||||
localStr := sch.ScheduledAt.In(loc).Format("2006-01-02 15:04")
|
||||
content := "Your session request with <@" + strconv.FormatInt(sch.InviteeID, 10) + "> for " +
|
||||
utcStr + " UTC (" + localStr + " " + zone + ") has been " + statusText + "."
|
||||
|
||||
requesterID := strconv.FormatInt(sch.RequesterID, 10)
|
||||
dmCh, err := s.UserChannelCreate(requesterID)
|
||||
if err == nil {
|
||||
if _, err := s.ChannelMessageSend(dmCh.ID, content); err != nil {
|
||||
log.Printf("schedule: failed to DM requester about status change: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleCreateUserSelect(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
values := i.MessageComponentData().Values
|
||||
if len(values) == 0 {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "No user selected.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
targetUserID := values[0]
|
||||
|
||||
if targetUserID == interactionUserID(i) {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You cannot schedule a session with yourself.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseModal,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
CustomID: "schedule:create_modal:" + targetUserID,
|
||||
Title: "Schedule a session",
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "date",
|
||||
Label: "Date (YYYY-MM-DD)",
|
||||
Style: discordgo.TextInputShort,
|
||||
Required: true,
|
||||
Placeholder: "2026-01-02",
|
||||
},
|
||||
},
|
||||
},
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "time",
|
||||
Label: "Time (HH:MM, your timezone)",
|
||||
Style: discordgo.TextInputShort,
|
||||
Required: true,
|
||||
Placeholder: "15:04",
|
||||
},
|
||||
},
|
||||
},
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "description",
|
||||
Label: "Description (optional)",
|
||||
Style: discordgo.TextInputShort,
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func modalTextInputValues(components []discordgo.MessageComponent) map[string]string {
|
||||
values := make(map[string]string)
|
||||
for _, c := range components {
|
||||
row, ok := c.(*discordgo.ActionsRow)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, rc := range row.Components {
|
||||
ti, ok := rc.(*discordgo.TextInput)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
values[ti.CustomID] = ti.Value
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func handleCreateModalSubmit(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate, inviteeID int64) {
|
||||
guildID, ok := shared.ParseGuildID(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
requesterID, err := strconv.ParseInt(interactionUserID(i), 10, 64)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Invalid user ID.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if requesterID == inviteeID {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You cannot schedule a session with yourself.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
values := modalTextInputValues(i.ModalSubmitData().Components)
|
||||
dateStr := strings.TrimSpace(values["date"])
|
||||
timeStr := strings.TrimSpace(values["time"])
|
||||
desc := strings.TrimSpace(values["description"])
|
||||
|
||||
loc, zone, _, err := services.Global.UserSettings.GetTimezone(ctx, requesterID)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
zone = "UTC"
|
||||
}
|
||||
|
||||
when, err := shared.ParseScheduleDatetime(dateStr+" "+timeStr, loc)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Invalid date/time. Use YYYY-MM-DD for the date and HH:MM for the time, in your timezone (" + zone + ").",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
when = when.In(time.UTC)
|
||||
|
||||
scheduleID, err := services.Global.Schedule.CreateSchedule(ctx, guildID, requesterID, inviteeID, when, desc)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Failed to create schedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
dmCh, err := s.UserChannelCreate(strconv.FormatInt(inviteeID, 10))
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Session created, but failed to DM the invited user (are DMs disabled?).",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Title: "New session request",
|
||||
Description: fmt.Sprintf("You have been invited to a session by <@%d>.", requesterID),
|
||||
Color: 0x4caf50,
|
||||
}
|
||||
utcStr := when.Format("2006-01-02 15:04")
|
||||
locInv, zoneInv, _, err := services.Global.UserSettings.GetTimezone(ctx, inviteeID)
|
||||
if err != nil {
|
||||
locInv = time.UTC
|
||||
zoneInv = "UTC"
|
||||
}
|
||||
localInvStr := when.In(locInv).Format("2006-01-02 15:04")
|
||||
embed.Fields = []*discordgo.MessageEmbedField{
|
||||
{Name: "When (UTC)", Value: utcStr},
|
||||
{Name: "When (" + zoneInv + ")", Value: localInvStr},
|
||||
}
|
||||
if desc != "" {
|
||||
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
|
||||
Name: "Description",
|
||||
Value: desc,
|
||||
})
|
||||
}
|
||||
|
||||
_, err = s.ChannelMessageSendComplex(dmCh.ID, &discordgo.MessageSend{
|
||||
Content: "Respond to this session request:",
|
||||
Embed: embed,
|
||||
Components: scheduleActionButtons(scheduleID),
|
||||
})
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Session created, but failed to DM the invited user.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Session request created and sent to <@" + strconv.FormatInt(inviteeID, 10) + ">.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func HandleModalSubmit(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
ctx := context.Background()
|
||||
action, idOrUserID := parseComponentCustomID(i.ModalSubmitData().CustomID)
|
||||
|
||||
if action == "create_modal" {
|
||||
handleCreateModalSubmit(ctx, s, i, idOrUserID)
|
||||
return
|
||||
}
|
||||
|
||||
scheduleID := idOrUserID
|
||||
if scheduleID == 0 {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Invalid schedule ID.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
modalValues := modalTextInputValues(i.ModalSubmitData().Components)
|
||||
datetimeInput := strings.TrimSpace(modalValues["date"]) + " " + strings.TrimSpace(modalValues["time"])
|
||||
sch, err := services.Global.Schedule.GetByID(ctx, scheduleID)
|
||||
if err != nil || sch == nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error retrieving schedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userIDStr := interactionUserID(i)
|
||||
userID, err := strconv.ParseInt(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error parsing user ID.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if userID != sch.InviteeID && userID != sch.RequesterID {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You are not authorized to perform this action.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
loc, _, _, err := services.Global.UserSettings.GetTimezone(ctx, userID)
|
||||
if err != nil {
|
||||
loc = sch.ScheduledAt.UTC().Location()
|
||||
}
|
||||
|
||||
newTime, err := shared.ParseScheduleDatetime(datetimeInput, loc)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Invalid date/time. Use YYYY-MM-DD for the date and HH:MM for the time.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
//Convert time to UTC before storing in the database
|
||||
newTime = newTime.UTC()
|
||||
|
||||
err = services.Global.Schedule.ProposeReschedule(ctx, scheduleID, newTime, userID)
|
||||
if err != nil {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error proposing reschedule.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
otherID := sch.RequesterID
|
||||
if userID == sch.RequesterID {
|
||||
otherID = sch.InviteeID
|
||||
}
|
||||
|
||||
dmCh, err := s.UserChannelCreate(strconv.FormatInt(otherID, 10))
|
||||
if err != nil {
|
||||
shared.RespondEphemeral(s, i, "Failed to DM the invited user (are DMs disabled?).")
|
||||
return
|
||||
}
|
||||
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Title: "Reschedule Proposal",
|
||||
Description: fmt.Sprintf("<@%d> has proposed a new time for your session request. Please respond.", userID),
|
||||
Color: 0x4caf50,
|
||||
}
|
||||
|
||||
// Show both UTC and invitee-local time if available.
|
||||
utcStr := newTime.Format("2006-01-02 15:04")
|
||||
locInv, zoneInv, _, err := services.Global.UserSettings.GetTimezone(context.Background(), otherID)
|
||||
if err != nil {
|
||||
locInv = time.UTC
|
||||
zoneInv = "UTC"
|
||||
}
|
||||
localInvStr := newTime.In(locInv).Format("2006-01-02 15:04")
|
||||
embed.Fields = []*discordgo.MessageEmbedField{
|
||||
{
|
||||
Name: "When (UTC)",
|
||||
Value: utcStr,
|
||||
},
|
||||
{
|
||||
Name: "When (" + zoneInv + ")",
|
||||
Value: localInvStr,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = s.ChannelMessageSendComplex(dmCh.ID, &discordgo.MessageSend{
|
||||
Content: "A new time has been proposed for your session:",
|
||||
Embed: embed,
|
||||
Components: rescheduleProposalButtons(scheduleID),
|
||||
})
|
||||
if err != nil {
|
||||
shared.RespondEphemeral(s, i, "Failed to DM the other participant.")
|
||||
return
|
||||
}
|
||||
|
||||
shared.RespondEphemeral(s, i, "Proposed new time sent to <@"+strconv.FormatInt(otherID, 10)+">.")
|
||||
|
||||
}
|
||||
|
||||
func parseComponentCustomID(customID string) (string, int64) {
|
||||
parts := strings.Split(customID, ":")
|
||||
if len(parts) != 3 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
action := parts[1]
|
||||
scheduleID, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
return action, scheduleID
|
||||
}
|
||||
|
||||
func interactionUserID(i *discordgo.InteractionCreate) string {
|
||||
if i.Member != nil && i.Member.User != nil {
|
||||
return i.Member.User.ID
|
||||
}
|
||||
if i.User != nil {
|
||||
return i.User.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user