diff --git a/commands/stats.go b/commands/stats.go index e464890..ccc5857 100644 --- a/commands/stats.go +++ b/commands/stats.go @@ -3,6 +3,7 @@ package commands import ( "bytes" "image/color" + "io" "math" "math/rand/v2" "slices" @@ -46,6 +47,35 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM } days = uint(in) } + var w io.WriterTo + var err error + if v, ok := opt["user"]; ok { + w, err = statsMember(s, i, days, v.UserValue(s).ID) + } else { + w, err = statsAll(s, i, days) + } + if err != nil { + if err = resp.IsEphemeral().SetMessage("Il y a eu une erreur...").Send(); err != nil { + logger.Alert("commands/stats.go - Sending error occurred", err.Error()) + } + return + } + b := new(bytes.Buffer) + _, err = w.WriteTo(b) + if err != nil { + logger.Alert("commands/stats.go - Writing png", err.Error()) + } + err = resp.AddFile(&discordgo.File{ + Name: "plot.png", + ContentType: "image/png", + Reader: b, + }).Send() + if err != nil { + logger.Alert("commands/stats.go - Sending response", err.Error()) + } +} + +func statsAll(s *discordgo.Session, i *discordgo.InteractionCreate, days uint) (io.WriterTo, error) { var rawData []*data if gokord.Debug { var rawCopaingData []*user.CopaingXP @@ -55,7 +85,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM Error if err != nil { logger.Alert("commands/stats.go - Fetching result", err.Error()) - return + return nil, err } rawData = make([]*data, len(rawCopaingData)) for in, d := range rawCopaingData { @@ -71,7 +101,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM res := gokord.DB.Raw(sql, i.GuildID, exp.TimeStampNDaysBefore(days)) if err := res.Scan(&rawDbData).Error; err != nil { logger.Alert("commands/stats.go - Fetching result", err.Error()) - return + return nil, err } rawData = make([]*data, len(rawDbData)) for in, d := range rawDbData { @@ -92,7 +122,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM var cp user.Copaing if err := gokord.DB.First(&cp, raw.CopaingID).Error; err != nil { logger.Alert("commands/stats.go - Finding copaing", err.Error(), "id", raw.CopaingID) - return + return nil, err } copaings[raw.CopaingID] = &cp } @@ -111,6 +141,76 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM }) } + return generatePlot(s, i, days, copaings, stats) +} + +func statsMember(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, discordID string) (io.WriterTo, error) { + cp := user.GetCopaing(discordID, i.GuildID) + var rawData []*data + if gokord.Debug { + var rawCopaingData []*user.CopaingXP + err := gokord.DB. + Where( + "guild_id = ? and created_at > ? and copaing_id = ?", + i.GuildID, exp.TimeStampNDaysBefore(days), discordID, + ). + Find(&rawCopaingData). + Error + if err != nil { + logger.Alert("commands/stats.go - Fetching result", err.Error()) + return nil, err + } + rawData = make([]*data, len(rawCopaingData)) + for in, d := range rawCopaingData { + rawData[in] = &data{ + CreatedAt: d.CreatedAt, + XP: int(d.XP), + CopaingID: int(d.CopaingID), + } + } + } else { + sql := `SELECT "created_at"::date::text, sum("xp") as xp, "copaing_id" FROM copaing_xps WHERE "guild_id" = ? and "created_at" > ? and "copaing_id" = ? GROUP BY "created_at"::date, "copaing_id"` + var rawDbData []dbData + res := gokord.DB.Raw(sql, i.GuildID, exp.TimeStampNDaysBefore(days), discordID) + if err := res.Scan(&rawDbData).Error; err != nil { + logger.Alert("commands/stats.go - Fetching result", err.Error()) + return nil, err + } + rawData = make([]*data, len(rawDbData)) + for in, d := range rawDbData { + rawData[in] = &data{ + CreatedAt: d.CreatedAt.Time, + XP: d.XP, + CopaingID: d.CopaingID, + } + } + } + + copaings := map[int]*user.Copaing{ + int(cp.ID): cp, + } + stats := map[int]*[]plotter.XY{} + + for _, raw := range rawData { + pts, ok := stats[raw.CopaingID] + if !ok { + pts = &[]plotter.XY{} + stats[raw.CopaingID] = pts + } + t := float64(raw.CreatedAt.Unix() - time.Now().Unix()) + if !gokord.Debug { + t = math.Ceil(t / (24 * 60 * 60)) + } + *pts = append(*pts, plotter.XY{ + X: t, + Y: float64(raw.XP), + }) + } + + return generatePlot(s, i, days, copaings, stats) +} + +func generatePlot(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, copaings map[int]*user.Copaing, stats map[int]*[]plotter.XY) (io.WriterTo, error) { p := plot.New() p.Title.Text = "Évolution de l'XP" p.X.Label.Text = "Jours" @@ -123,7 +223,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM m, err := s.GuildMember(i.GuildID, c.DiscordID) if err != nil { logger.Alert("commands/stats.go - Fetching guild member", err.Error()) - return + return nil, err } slices.SortFunc(*stats[in], func(a, b plotter.XY) int { if a.X < b.X { @@ -149,7 +249,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM l, err := plotter.NewLine(plotter.XYs(*stats[in])) if err != nil { logger.Alert("commands/stats.go - Adding line points", err.Error()) - return + return nil, err } l.LineStyle.Width = vg.Points(1) l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)} @@ -160,19 +260,7 @@ func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionM w, err := p.WriterTo(8*vg.Inch, 6*vg.Inch, "png") if err != nil { logger.Alert("commands/stats.go - Generating png", err.Error()) - return - } - b := new(bytes.Buffer) - _, err = w.WriteTo(b) - if err != nil { - logger.Alert("commands/stats.go - Writing png", err.Error()) - } - err = resp.AddFile(&discordgo.File{ - Name: "plot.png", - ContentType: "image/png", - Reader: b, - }).Send() - if err != nil { - logger.Alert("commands/stats.go - Sending response", err.Error()) + return nil, err } + return w, nil } diff --git a/main.go b/main.go index e2856eb..93b75d7 100644 --- a/main.go +++ b/main.go @@ -85,6 +85,16 @@ func main() { SetHandler(commands.Credits) statsCmd := cmd.New("stats", "Affiche des stats :D"). + AddOption(cmd.NewOption( + discordgo.ApplicationCommandOptionInteger, + "days", + "Nombre de jours à afficher dans le graphique", + )). + AddOption(cmd.NewOption( + discordgo.ApplicationCommandOptionUser, + "user", + "Utilisateur à inspecter", + )). SetHandler(commands.Stats) innovations, err := gokord.LoadInnovationFromJson(updatesData) diff --git a/user/member.go b/user/member.go index afc94a6..ad8762f 100644 --- a/user/member.go +++ b/user/member.go @@ -4,7 +4,6 @@ import ( "time" "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/logger" ) type Copaing struct { @@ -35,15 +34,7 @@ const ( func GetCopaing(discordID string, guildID string) *Copaing { c := Copaing{DiscordID: discordID, GuildID: guildID} if err := c.Load(); err != nil { - logger.Alert( - "user/member.go - Loading user", - err.Error(), - "discord_id", - discordID, - "guild_id", - guildID, - ) - return nil + panic(err) } return &c }