save first version
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
package claim
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/leolionad58/ticketbot/internal/config"
|
||||
"github.com/leolionad58/ticketbot/internal/db"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
cfg *config.Provider
|
||||
repo *db.TicketRepo
|
||||
claims *db.ClaimMessageRepo
|
||||
mu sync.Mutex
|
||||
stops map[int64]context.CancelFunc
|
||||
}
|
||||
|
||||
func NewManager(cfg *config.Provider, repo *db.TicketRepo, claims *db.ClaimMessageRepo) *Manager {
|
||||
return &Manager{
|
||||
cfg: cfg,
|
||||
repo: repo,
|
||||
claims: claims,
|
||||
stops: make(map[int64]context.CancelFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// StartTicket posts the initial claim message and starts the re-up goroutine.
|
||||
func (m *Manager) StartTicket(ctx context.Context, s *discordgo.Session, ticket *db.Ticket) {
|
||||
msgID, err := m.postClaim(ctx, s, ticket, false)
|
||||
if err != nil {
|
||||
slog.Error("claim: post initial message", "ticket_id", ticket.ID, "err", err)
|
||||
} else {
|
||||
if err := m.claims.Upsert(ctx, ticket.ID, msgID, time.Now()); err != nil {
|
||||
slog.Error("claim: upsert claim_message", "ticket_id", ticket.ID, "err", err)
|
||||
}
|
||||
}
|
||||
m.startReupLoop(s, ticket, time.Now())
|
||||
}
|
||||
|
||||
// ResumeTicket re-attaches the re-up loop after a bot restart.
|
||||
func (m *Manager) ResumeTicket(s *discordgo.Session, ticket *db.Ticket, lastReupAt time.Time) {
|
||||
m.startReupLoop(s, ticket, lastReupAt)
|
||||
}
|
||||
|
||||
// Stop cancels the re-up loop for a ticket (call on claim or close).
|
||||
func (m *Manager) Stop(ticketID int64) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if cancel, ok := m.stops[ticketID]; ok {
|
||||
cancel()
|
||||
delete(m.stops, ticketID)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteClaimMessage removes the claim message from the claim channel.
|
||||
func (m *Manager) DeleteClaimMessage(ctx context.Context, s *discordgo.Session, ticketID int64) {
|
||||
cm, err := m.claims.Get(ctx, ticketID)
|
||||
if err != nil || cm == nil {
|
||||
return
|
||||
}
|
||||
cfg := m.cfg.Get()
|
||||
if err := s.ChannelMessageDelete(cfg.Bot.ClaimChannel, cm.MessageID); err != nil {
|
||||
slog.Warn("claim: delete message", "ticket_id", ticketID, "err", err)
|
||||
}
|
||||
m.claims.Delete(ctx, ticketID) //nolint
|
||||
}
|
||||
|
||||
func (m *Manager) startReupLoop(s *discordgo.Session, ticket *db.Ticket, lastReupAt time.Time) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m.mu.Lock()
|
||||
// Cancel previous loop if any (shouldn't happen in normal flow)
|
||||
if old, ok := m.stops[ticket.ID]; ok {
|
||||
old()
|
||||
}
|
||||
m.stops[ticket.ID] = cancel
|
||||
m.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
m.mu.Lock()
|
||||
delete(m.stops, ticket.ID)
|
||||
m.mu.Unlock()
|
||||
}()
|
||||
|
||||
cfg := m.cfg.Get()
|
||||
interval := time.Duration(cfg.Bot.ClaimReupMinutes) * time.Minute
|
||||
elapsed := time.Since(lastReupAt)
|
||||
next := interval - elapsed
|
||||
if next <= 0 {
|
||||
next = time.Second // fire almost immediately if already overdue
|
||||
}
|
||||
|
||||
timer := time.NewTimer(next)
|
||||
defer timer.Stop()
|
||||
|
||||
reupCount := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-timer.C:
|
||||
// Verify ticket is still open before re-posting
|
||||
checkCtx, checkCancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
t, err := m.repo.GetByID(checkCtx, ticket.ID)
|
||||
checkCancel()
|
||||
if err != nil || t == nil || t.Status != "open" {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete old claim message
|
||||
delCtx, delCancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
m.DeleteClaimMessage(delCtx, s, ticket.ID)
|
||||
delCancel()
|
||||
|
||||
// Re-post with staff ping (first re-up and beyond)
|
||||
postCtx, postCancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
msgID, postErr := m.postClaim(postCtx, s, ticket, reupCount >= 0)
|
||||
postCancel()
|
||||
if postErr != nil {
|
||||
slog.Error("claim reup: post", "ticket_id", ticket.ID, "err", postErr)
|
||||
} else {
|
||||
saveCtx, saveCancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
m.claims.Upsert(saveCtx, ticket.ID, msgID, time.Now()) //nolint
|
||||
saveCancel()
|
||||
}
|
||||
reupCount++
|
||||
|
||||
// Re-read config in case of hot reload
|
||||
cfg = m.cfg.Get()
|
||||
interval = time.Duration(cfg.Bot.ClaimReupMinutes) * time.Minute
|
||||
timer.Reset(interval)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *Manager) postClaim(ctx context.Context, s *discordgo.Session, ticket *db.Ticket, withPing bool) (string, error) {
|
||||
cfg := m.cfg.Get()
|
||||
claimChannelID := cfg.Bot.ClaimChannel
|
||||
if claimChannelID == "" {
|
||||
return "", fmt.Errorf("claim_channel not configured")
|
||||
}
|
||||
|
||||
elapsed := time.Since(ticket.OpenedAt).Round(time.Second)
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Title: "Nouveau ticket à claim",
|
||||
Color: 0x5865f2,
|
||||
Fields: []*discordgo.MessageEmbedField{
|
||||
{Name: "Type", Value: ticket.Type, Inline: true},
|
||||
{Name: "Panel", Value: ticket.Panel, Inline: true},
|
||||
{Name: "Utilisateur", Value: fmt.Sprintf("<@%s>", ticket.UserID), Inline: true},
|
||||
{Name: "Numéro", Value: fmt.Sprintf("%04d", ticket.TicketNumber), Inline: true},
|
||||
{Name: "Channel", Value: fmt.Sprintf("<#%s>", ticket.ChannelID), Inline: true},
|
||||
{Name: "Ouvert il y a", Value: elapsed.String(), Inline: true},
|
||||
},
|
||||
}
|
||||
|
||||
customID := fmt.Sprintf("claim:take:%d", ticket.ID)
|
||||
send := &discordgo.MessageSend{
|
||||
Embeds: []*discordgo.MessageEmbed{embed},
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{Components: []discordgo.MessageComponent{
|
||||
discordgo.Button{
|
||||
Label: "Claim",
|
||||
Style: discordgo.SuccessButton,
|
||||
CustomID: customID,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
if withPing {
|
||||
if panel, ok := cfg.Panels[ticket.Panel]; ok {
|
||||
if t, ok := panel.Types[ticket.Type]; ok && t.StaffRole != "" {
|
||||
send.Content = fmt.Sprintf("<@&%s>", t.StaffRole)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := s.ChannelMessageSendComplex(claimChannelID, send)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return msg.ID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user