diff --git a/commands/commands.go b/commands/commands.go index 022d20d..5bfcaea 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -18,6 +18,7 @@ type command struct { var commands = []command{ command{"autorole", " ", handleAutorole, 3, true}, + command{"recalclevel", "", handleRecalculateLevel, 0, false}, } func Event(dg *discordgo.Session, m *discordgo.MessageCreate) { diff --git a/commands/recalcLevel.go b/commands/recalcLevel.go new file mode 100644 index 0000000..dee0d91 --- /dev/null +++ b/commands/recalcLevel.go @@ -0,0 +1,49 @@ +package commands + +import ( + "fmt" + "math/rand" + "time" + + "github.com/bwmarrin/discordgo" + + "gitea.teamortix.com/chanbakjsd/Milen/level" + "gitea.teamortix.com/chanbakjsd/Milen/util" +) + +func handleRecalculateLevel(dg *discordgo.Session, m *discordgo.MessageCreate, arguments []string) { + if len(arguments) == 0 { + util.SendFailEmbed( + dg, m.ChannelID, "Bot Owner Access Required", + "⚠️ This action might take a while and disable level calculation system.\nTo confirm, check console for launch code.", + ) + fmt.Println("Level launch requested: " + calculationCode) + return + } + if arguments[0] != calculationCode { + util.SendFailEmbed( + dg, m.ChannelID, "Incorrect Launch Code", "You entered an incorrect code.", + ) + } + util.SendSuccessEmbed(dg, m.ChannelID, "Level is being recalculated.") + level.RecalculateEverything(dg, m.GuildID) + rand.Seed(time.Now().UnixNano()) + calculationCode = randomBase64(20) +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+=" + +var calculationCode string + +func init() { + rand.Seed(time.Now().UnixNano()) + calculationCode = randomBase64(20) +} + +func randomBase64(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +} diff --git a/db/db.go b/db/db.go index 9ba43b4..d0d9641 100644 --- a/db/db.go +++ b/db/db.go @@ -5,12 +5,6 @@ import ( _ "github.com/jinzhu/gorm/dialects/sqlite" ) -type ReactRole struct { - MessageID string - EmojiID string - RoleID string -} - var db *gorm.DB func init() { @@ -20,25 +14,9 @@ func init() { panic(err) } db.AutoMigrate(&ReactRole{}) + db.AutoMigrate(&PlayerLevel{}) } func Close() { db.Close() } - -func CreateReactRole(messageID, emojiID, roleID string) { - db.Create(&ReactRole{ - MessageID: messageID, - EmojiID: emojiID, - RoleID: roleID, - }) -} - -func GetReactRole(messageID, emojiID string) string { - result := ReactRole{ - MessageID: messageID, - EmojiID: emojiID, - } - db.Where(&result).First(&result) - return result.RoleID -} diff --git a/db/level.go b/db/level.go new file mode 100644 index 0000000..503b380 --- /dev/null +++ b/db/level.go @@ -0,0 +1,40 @@ +package db + +import ( + "fmt" + "time" + + "github.com/jinzhu/gorm" +) + +type PlayerLevel struct { + UserID string `gorm:"primary_key"` + LastActive time.Time + XP int64 +} + +func IncrementXP(userID string, value int64) { + db.Where(&PlayerLevel{UserID: userID}). + UpdateColumn("xp", gorm.Expr("xp + ?", value)). + UpdateColumn("last_active", time.Now()) +} + +func GetLastActive(userID string) time.Time { + result := PlayerLevel{ + UserID: userID, + } + db.Where(&result).First(&result) + return result.LastActive +} + +func DeleteAllLevel() { + db.Delete(&PlayerLevel{}) +} + +func SetXP(userID string, lastActive time.Time, xp int64) { + db.Create(&PlayerLevel{ + UserID: userID, + LastActive: lastActive, + XP: xp, + }) +} diff --git a/db/reactRole.go b/db/reactRole.go new file mode 100644 index 0000000..2181fd1 --- /dev/null +++ b/db/reactRole.go @@ -0,0 +1,24 @@ +package db + +type ReactRole struct { + MessageID string + EmojiID string + RoleID string +} + +func CreateReactRole(messageID, emojiID, roleID string) { + db.Create(&ReactRole{ + MessageID: messageID, + EmojiID: emojiID, + RoleID: roleID, + }) +} + +func GetReactRole(messageID, emojiID string) string { + result := ReactRole{ + MessageID: messageID, + EmojiID: emojiID, + } + db.Where(&result).First(&result) + return result.RoleID +} diff --git a/level/listener.go b/level/listener.go new file mode 100644 index 0000000..494393c --- /dev/null +++ b/level/listener.go @@ -0,0 +1,13 @@ +package level + +import ( + "github.com/bwmarrin/discordgo" +) + +var ShouldListen = true + +func Event(dg *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.Bot || ShouldListen { + return + } +} diff --git a/level/recalculateEngine.go b/level/recalculateEngine.go new file mode 100644 index 0000000..d53ba45 --- /dev/null +++ b/level/recalculateEngine.go @@ -0,0 +1,98 @@ +package level + +import ( + "fmt" + "sort" + "time" + + "github.com/bwmarrin/discordgo" + + "gitea.teamortix.com/chanbakjsd/Milen/db" + "gitea.teamortix.com/chanbakjsd/Milen/util" +) + +func RecalculateEverything(dg *discordgo.Session, guildID string) { + ShouldListen = false + defer func() { + ShouldListen = true + }() + + startTime := time.Now() + guild, err := dg.Guild(guildID) + if err != nil { + util.ReportError(dg, err) + return + } + listOfMessages := make(map[string][]*discordgo.Message) + for _, v := range guild.Channels { + beforeID := "" + for { + messages, err := dg.ChannelMessages(v.ID, 100, beforeID, "", "") + if err != nil { + util.ReportError(dg, err) + return + } + for _, msg := range messages { + if list, ok := listOfMessages[msg.Author.ID]; ok { + listOfMessages[msg.Author.ID] = append(list, msg) + } else { + listOfMessages[msg.Author.ID] = []*discordgo.Message{msg} + } + } + if len(messages) < 100 { + break + } + beforeID = messages[99].ID + } + } + fetchedTime := time.Now() + totalXP := make(map[string]int64) + for k, v := range listOfMessages { + sort.Slice(v, func(i, j int) bool { + return v[i].ID < v[j].ID + }) + prevTime, err := discordgo.SnowflakeTimestamp(v[0].ID) + if err != nil { + util.ReportError(dg, err) + return + } + for i := 1; i < len(v); i++ { + nextTime, err := discordgo.SnowflakeTimestamp(v[i].ID) + if err != nil { + util.ReportError(dg, err) + return + } + xp := int64(nextTime.Sub(prevTime).Seconds()) + if xp > int64(len(v[i].Content)-3)/3 { + xp = int64(len(v[i].Content)-3) / 3 + } + if xp > 10 { + xp = 10 + } + if xp >= 0 { + totalXP[k] += xp + } + prevTime = nextTime + } + } + + processTime := time.Now() + db.DeleteAllLevel() + for k, v := range totalXP { + userHistory := listOfMessages[k] + newestMessage := userHistory[len(userHistory)-1] + time, err := discordgo.SnowflakeTimestamp(newestMessage.ID) + if err != nil { + util.ReportError(dg, err) + return + } + db.SetXP(k, time, v) + } + + finishTime := time.Now() + + fmt.Printf("[RECALC] Fetch took %v.\n", fetchedTime.Sub(startTime)) + fmt.Printf("[RECALC] XP calculation took %v.\n", processTime.Sub(fetchedTime)) + fmt.Printf("[RECALC] DB write took %v.\n", finishTime.Sub(processTime)) + fmt.Printf("[RECALC] Total took %v.\n", finishTime.Sub(startTime)) +}