package public import ( "context" "errors" "fmt" "log" "strings" "time" "velox-bot/internal/db/services" "velox-bot/internal/tldr" "github.com/bwmarrin/discordgo" ) var dmPermission = false var Tldr = &discordgo.ApplicationCommand{ Name: "tldr", Description: "Privately summarize what you missed in this channel", DMPermission: &dmPermission, Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "since", Description: "Look back this far instead of since your last /tldr (e.g. 30m, 6h, 2d; max 7d)", Required: false, }, }, } const embedColor = 0xFFA500 func TldrHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { if i.GuildID == "" || i.Member == nil || i.Member.User == nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "`/tldr` only works in a server.", Flags: discordgo.MessageFlagsEphemeral, }, }) return } var since time.Duration var sinceText string for _, opt := range i.ApplicationCommandData().Options { if opt.Name != "since" { continue } d, err := tldr.ParseSince(opt.StringValue()) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: err.Error(), Flags: discordgo.MessageFlagsEphemeral, }, }) return } since, sinceText = d, strings.ToLower(strings.TrimSpace(opt.StringValue())) } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Reading what you missed…", Flags: discordgo.MessageFlagsEphemeral, }, }) edit := func(content string) { _, _ = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{Content: &content}) } userID := i.Member.User.ID if !tldr.TryStart(userID) { edit("You already have a summary running, give it a moment.") return } defer tldr.Done(userID) ctx, cancel := context.WithTimeout(context.Background(), tldr.RunTimeout()) defer cancel() // While the model writes, show the overview growing instead of leaving the // "Summarizing…" text up for the whole wait. The final edit below replaces it. showPartial := func(text string) { content := "" embeds := []*discordgo.MessageEmbed{{ Title: "TL;DR", Description: clip(text, 4000), Color: embedColor, Footer: &discordgo.MessageEmbedFooter{Text: "still writing…"}, }} _, _ = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{Content: &content, Embeds: &embeds}) } res, err := tldr.Run(ctx, s, services.Global.TLDR, tldr.Default(), tldr.Request{ GuildID: i.GuildID, ChannelID: i.ChannelID, UserID: userID, BotID: s.State.User.ID, UserPerms: i.Member.Permissions, Since: since, }, edit, showPartial) if errors.Is(err, tldr.ErrUserCannotRead) { edit("You need to be able to read this channel's message history to summarize it.") return } if errors.Is(err, tldr.ErrBotCannotRead) { edit("I can't read this channel's history. I need View Channel and Read Message History here.") return } if err != nil { log.Printf("tldr: guild %s user %s: %v", i.GuildID, userID, err) edit("I couldn't put a summary together right now. The summarizer may be down or busy, try again in a bit.") return } if res.Empty { edit("Nothing new to summarize.") return } scope := "since your last /tldr here" if since > 0 { scope = "last " + sinceText } footer := fmt.Sprintf("%d messages · %s", res.Messages, scope) if res.Truncated { if since > 0 { footer += " · more than fit, the most recent were left out, try a shorter window" } else { footer += " · more than fit, run /tldr again for the rest" } } empty := "" embeds := []*discordgo.MessageEmbed{{ Title: "TL;DR", Description: clip(res.Summary, 4000), Color: embedColor, Footer: &discordgo.MessageEmbedFooter{Text: footer}, }} _, _ = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ Content: &empty, Embeds: &embeds, }) } func clip(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(r[:n-1]) + "…" }