From d0404bd46d24a8625c280d8edaa389270e8d93b0 Mon Sep 17 00:00:00 2001 From: Levatax <levatax@randomchars.net> Date: Fri, 9 Jul 2021 13:19:46 +0300 Subject: [PATCH] fix guild ID handling, optional user information to ticket setup, persisting of ticket state on disk --- .gitignore | 1 + cleanup.go | 7 +++--- config.go | 2 ++ handler.go | 13 ++++++----- main.go | 19 ++++++++++++++++ ticket.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 96 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 6cb095e..eefd3c1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ *.test *.out /.idea/ +/store/ ticket-bot ticket.conf \ No newline at end of file diff --git a/cleanup.go b/cleanup.go index 21e2de3..b200b8a 100644 --- a/cleanup.go +++ b/cleanup.go @@ -2,12 +2,13 @@ package main import ( log "git.randomchars.net/FreeNitori/Log" - "os" ) func cleanup() { + dumpPath := config.Store + "/state" if err := session.Close(); err != nil { - log.Fatalf("Error while closing session, %s", err) - os.Exit(1) + log.Warnf("Error while closing session, %s", err) } + log.Infof("Dumping ticket state to %s.", dumpPath) + dumpTicketState(dumpPath) } diff --git a/config.go b/config.go index 6c66f4c..dc1b2ec 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,7 @@ var defaultConfig = configPayload{ MessageID: []string{}, GuildID: "GUILD_ID", Prefix: "!", + Store: "./store", } type configPayload struct { @@ -21,6 +22,7 @@ type configPayload struct { MessageID []string GuildID string Prefix string + Store string } func init() { diff --git a/handler.go b/handler.go index 91df2de..c0ce299 100644 --- a/handler.go +++ b/handler.go @@ -19,7 +19,7 @@ func handleReaction(context *multiplexer.Context) { return } - if reactionAdd.GuildID != config.GuildID { + if reactionAdd.GuildID == "" { return } if reactionAdd.UserID == session.State.User.ID { @@ -31,13 +31,13 @@ func handleReaction(context *multiplexer.Context) { switch reactionAdd.Emoji.Name { case "1️⃣": - setupTicket(reactionAdd.GuildID, reactionAdd.UserID, 0) + setupTicket(config.GuildID, reactionAdd.UserID, 0, context.User) case "2️⃣": - setupTicket(reactionAdd.GuildID, reactionAdd.UserID, 1) + setupTicket(config.GuildID, reactionAdd.UserID, 1, context.User) case "3️⃣": - setupTicket(reactionAdd.GuildID, reactionAdd.UserID, 2) + setupTicket(config.GuildID, reactionAdd.UserID, 2, context.User) case "4️⃣": - setupTicket(reactionAdd.GuildID, reactionAdd.UserID, 3) + setupTicket(config.GuildID, reactionAdd.UserID, 3, context.User) default: return } @@ -60,6 +60,9 @@ func handleMessages(context *multiplexer.Context) { case true: sendMessage(instance.ChannelID, context.Message.Content) case false: + if context.Message.Content == context.Prefix() + "close" { + return + } sendMessage(instance.UserChannelID, context.Message.Content) } } diff --git a/main.go b/main.go index 26f5599..65b5469 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,19 @@ func main() { flag.Parse() parse() + // Ensure store directory + if _, err := os.ReadDir(config.Store); err != nil { + if os.IsNotExist(err) { + log.Warnf("Creating store directory on %s.", config.Store) + if err = os.MkdirAll(config.Store, 0700); err != nil { + log.Fatalf("Error creating store directory on %s, %s", config.Store, err) + os.Exit(1) + } + } else { + log.Fatalf("Store directory inaccessible, %s.", err) + } + } + // Set discordgo log handler discordgo.Logger = func(msgL, _ int, format string, a ...interface{}) { var level logrus.Level @@ -53,6 +66,12 @@ func main() { session.ShouldReconnectOnError = true session.Identify.Intents = discordgo.IntentsAllWithoutPrivileged + if state, ok := readTicketState(config.Store + "/state"); ok == true { + ticketInstancesUser = state.TicketInstancesUser + ticketInstancesChannel = state.TicketInstancesChannel + log.Infof("Read %v ticket states from %s.", len(ticketInstancesChannel), config.Store + "/state") + } + // Open session if err := session.Open(); err != nil { log.Fatalf("Error while opening session, %s", err) diff --git a/ticket.go b/ticket.go index 8e63004..2c23527 100644 --- a/ticket.go +++ b/ticket.go @@ -1,12 +1,16 @@ package main import ( + "encoding/json" "fmt" log "git.randomchars.net/FreeNitori/Log" "github.com/bwmarrin/discordgo" + "os" + "sync" "time" ) +var ticketLock = sync.RWMutex{} var ticketInstancesChannel = make(map[string]*ticket) var ticketInstancesUser = make(map[string]*ticket) @@ -17,7 +21,15 @@ type ticket struct { CreationDate time.Time `json:"creation_date"` } +type ticketState struct { + TicketInstancesChannel map[string]*ticket `json:"ticket_instances_channel"` + TicketInstancesUser map[string]*ticket `json:"ticket_instances_user"` +} + func (t *ticket) delete() { + ticketLock.RLock() + defer ticketLock.RUnlock() + delete(ticketInstancesUser, t.UserID) delete(ticketInstancesChannel, t.ChannelID) if _, err := session.ChannelDelete(t.ChannelID); err != nil { @@ -50,7 +62,9 @@ func createTicket(channelID, userID string) *ticket { return instance } -func setupTicket(guildID, userID string, subject int) bool { +func setupTicket(guildID, userID string, subject int, user *discordgo.User) bool { + ticketLock.RLock() + defer ticketLock.RUnlock() if channel, err := session.GuildChannelCreate(guildID, "ticket-"+userID, discordgo.ChannelTypeGuildText); err != nil { log.Warnf("Error creating ticket channel for user %s, %s", userID, err) return false @@ -64,6 +78,52 @@ func setupTicket(guildID, userID string, subject int) bool { return false } } - return sendMessage(channel.ID, fmt.Sprintf("Ticket created with subject %v.", subject)) + if user != nil { + return sendMessage(channel.ID, fmt.Sprintf("Ticket created by user %s#%s (%s) with subject %v.", + user.Username, user.Discriminator, user.ID, subject)) + } else { + return sendMessage(channel.ID, fmt.Sprintf("Ticket created with subject %v.", subject)) + } + } +} + +func makeActiveTicketsPayload() []byte { + if payload, err := json.Marshal(ticketState{ + TicketInstancesChannel: ticketInstancesChannel, + TicketInstancesUser: ticketInstancesUser, + }); err != nil { + log.Fatalf("Error saving ticket state, %s, all ticket changes since previous start will be lost.") + os.Exit(1) + } else { + return payload + } + return nil +} + +func dumpTicketState(path string) { + // Permanently lock ticket state until program exit + ticketLock.Lock() + + if err := os.WriteFile(path, makeActiveTicketsPayload(), 0600); err != nil { + log.Errorf("Error dumping ticket state, %s, all ticket changes since previous start will be lost.") + } +} + +func readTicketState(path string) (ticketState, bool) { + var state ticketState + if payload, err := os.ReadFile(path); err != nil { + if os.IsNotExist(err) { + return state, false + } + log.Fatalf("Error reading state file, %s", err) + os.Exit(1) + } else { + if err = json.Unmarshal(payload, &state); err != nil { + log.Fatalf("Error parsing state file, %s", err) + os.Exit(1) + } else { + return state, true + } } + return state, false } -- GitLab