owobot/internal/systems/tickets/init.go

214 lines
5.7 KiB
Go

package tickets
import (
"bytes"
"fmt"
"io"
"github.com/bwmarrin/discordgo"
"go.elara.ws/logger/log"
"go.elara.ws/owobot/internal/cache"
"go.elara.ws/owobot/internal/db"
"go.elara.ws/owobot/internal/systems/commands"
"go.elara.ws/owobot/internal/systems/eventlog"
"go.elara.ws/owobot/internal/util"
)
const ticketPermissions = discordgo.PermissionSendMessages | discordgo.PermissionViewChannel | discordgo.PermissionReadMessageHistory
func Init(s *discordgo.Session) error {
s.AddHandler(onMemberLeave)
commands.Register(s, ticketCmd, &discordgo.ApplicationCommand{
Name: "ticket",
Description: "Open a ticket to talk to the mods",
})
commands.Register(s, ticketCategoryCmd, &discordgo.ApplicationCommand{
Name: "ticket_category",
Description: "Set the category in which to create ticket channels",
DefaultMemberPermissions: util.Pointer[int64](discordgo.PermissionManageServer),
Options: []*discordgo.ApplicationCommandOption{
{
Name: "category",
Description: "The category to put ticket channels in",
Type: discordgo.ApplicationCommandOptionChannel,
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildCategory},
Required: true,
},
},
})
commands.Register(s, modTicketCmd, &discordgo.ApplicationCommand{
Name: "mod_ticket",
Description: "Open a ticket for a user to talk to the mods",
DefaultMemberPermissions: util.Pointer[int64](discordgo.PermissionManageChannels),
Options: []*discordgo.ApplicationCommandOption{
{
Name: "user",
Description: "The user to open a ticket for",
Type: discordgo.ApplicationCommandOptionUser,
Required: true,
},
},
})
commands.Register(s, closeTicketCmd, &discordgo.ApplicationCommand{
Name: "close_ticket",
Description: "Close a user's ticket",
DefaultMemberPermissions: util.Pointer[int64](discordgo.PermissionManageChannels),
Options: []*discordgo.ApplicationCommandOption{
{
Name: "user",
Description: "The user whose ticket to close",
Type: discordgo.ApplicationCommandOptionUser,
Required: true,
},
},
})
return nil
}
// Open opens a new ticket. It checks if a ticket already exists, and if not, creates a new channel for it,
// allows the user it's for to see and send messages in it, adds it to the database, and logs the ticket open.
func Open(s *discordgo.Session, guildID string, user, executor *discordgo.User) (string, error) {
channelID, err := db.TicketChannelID(guildID, user.ID)
if err == nil {
return "", fmt.Errorf("ticket already exists for %s at <#%s>", user.Mention(), channelID)
}
if executor == nil {
executor = s.State.User
}
guild, err := db.GuildByID(guildID)
if err != nil {
return "", err
}
overwrites := []*discordgo.PermissionOverwrite{{
ID: user.ID,
Type: discordgo.PermissionOverwriteTypeMember,
Allow: ticketPermissions,
}}
if guild.TicketCategoryID != "" {
category, err := cache.Channel(s, guildID, guild.TicketCategoryID)
if err != nil {
log.Error("Error getting ticket category").Err(err).Send()
// If we can't get the ticket category, set it to empty string
// so that ChannelCreate doesn't try to use it.
guild.TicketCategoryID = ""
} else {
overwrites = append(overwrites, category.PermissionOverwrites...)
}
}
c, err := s.GuildChannelCreateComplex(guildID, discordgo.GuildChannelCreateData{
Name: "ticket-" + user.Username,
Type: discordgo.ChannelTypeGuildText,
ParentID: guild.TicketCategoryID,
PermissionOverwrites: overwrites,
})
if err != nil {
return "", err
}
err = db.AddTicket(guildID, user.ID, c.ID)
if err != nil {
return "", err
}
return c.ID, eventlog.Log(s, guildID, eventlog.Entry{
Title: "New ticket opened!",
Description: "**Executed by:** " + executor.Mention(),
Author: user,
})
}
// Close closes the given user's ticket. It gets the channel ID of the ticket, logs all the messages
// inside it, deletes the channel, removes the ticket from the database, and logs the ticket close.
func Close(s *discordgo.Session, guildID string, user, executor *discordgo.User) error {
channelID, err := db.TicketChannelID(guildID, user.ID)
if err != nil {
return err
}
if executor == nil {
executor = s.State.User
}
guild, err := db.GuildByID(guildID)
if err != nil {
return err
}
if guild.TicketLogChanID != "" {
buf, err := getChannelMessageLog(s, channelID)
if err != nil {
return err
}
if buf != nil {
err = eventlog.TicketMsgLog(s, guildID, buf)
if err != nil {
return err
}
}
}
_, err = s.ChannelDelete(channelID)
if err != nil {
return err
}
err = db.RemoveTicket(guildID, user.ID)
if err != nil {
return err
}
return eventlog.Log(s, guildID, eventlog.Entry{
Title: "Ticket Closed",
Description: "**Executed by:** " + executor.Mention(),
Author: user,
})
}
// getChannelMessageLog generates a log for the given channel. It retrieves all the messages
// inside it and writes them to a buffer.
func getChannelMessageLog(s *discordgo.Session, channelID string) (*bytes.Buffer, error) {
out := &bytes.Buffer{}
msgs, err := s.ChannelMessages(channelID, 100, "", "", "")
if err != nil {
return nil, err
}
if len(msgs) == 0 {
return nil, nil
}
err = writeMsgs(msgs, out)
if err != nil {
return nil, err
}
return out, nil
}
// writeMsgs writes a slice of messages to w.
func writeMsgs(msgs []*discordgo.Message, w io.Writer) error {
if len(msgs) == 0 {
return nil
}
for i := len(msgs) - 1; i >= 0; i-- {
_, err := io.WriteString(w, fmt.Sprintf("%s - %s\n", msgs[i].Author.Username, msgs[i].Content))
if err != nil {
return err
}
}
return nil
}