diff options
| author | William Hergès <william@herges.fr> | 2025-09-27 18:23:27 +0200 |
|---|---|---|
| committer | William Hergès <william@herges.fr> | 2025-09-27 18:23:27 +0200 |
| commit | b1d3bca64702e66b5ecfe5c9ea5f43aa9dc1d1e6 (patch) | |
| tree | 67474ed704f529fe4941c179b7697b54099cc326 /commands/stats.go | |
| parent | c46d1c34a29b10dac2a059b9d78e99a3d5d76f96 (diff) | |
| parent | cfdba5f417bb31aac564d13becc09874f17d075d (diff) | |
Merge branch 'main' into feat/xp-boostfeat/xp-boost
Diffstat (limited to 'commands/stats.go')
| -rw-r--r-- | commands/stats.go | 118 |
1 files changed, 74 insertions, 44 deletions
diff --git a/commands/stats.go b/commands/stats.go index f93f6a0..4fc35ae 100644 --- a/commands/stats.go +++ b/commands/stats.go @@ -3,11 +3,10 @@ package commands import ( "bytes" "errors" - "gorm.io/gorm" + "fmt" "image/color" "io" "math" - "math/rand/v2" "slices" "time" @@ -16,12 +15,15 @@ import ( "git.anhgelus.world/anhgelus/les-copaings-bot/user" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/cmd" - "github.com/anhgelus/gokord/logger" "github.com/jackc/pgx/v5/pgtype" - discordgo "github.com/nyttikord/gokord" + "github.com/nyttikord/gokord/bot" + "github.com/nyttikord/gokord/channel" + "github.com/nyttikord/gokord/event" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/vg" + "gonum.org/v1/plot/vg/draw" + "gorm.io/gorm" ) type data struct { @@ -36,78 +38,91 @@ type dbData struct { CopaingID int } -func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { +var colors = []color.RGBA{ + {38, 70, 83, 255}, + {42, 157, 143, 255}, + {244, 162, 97, 255}, + {231, 111, 81, 255}, + {193, 18, 31, 255}, +} + +func Stats(s bot.Session, i *event.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { cfg := config.GetGuildConfig(i.GuildID) - days := cfg.DaysXPRemains + days := 15 + if gokord.Debug { + days = 90 + } if v, ok := opt["days"]; ok { in := v.IntValue() - if in < 0 || uint(in) > days { - if err := resp.SetMessage("Nombre de jours invalide").IsEphemeral().Send(); err != nil { - logger.Alert("commands/stats.go - Sending invalid days", err.Error()) + if in < 1 || uint(in) > cfg.DaysXPRemains { + msg := fmt.Sprintf("Nombre de jours invalide. Il doit être strictement positif et inférieur à %d", cfg.DaysXPRemains) + if err := resp.SetMessage(msg).IsEphemeral().Send(); err != nil { + s.Logger().Error("sending error invalid days", "error", err) } return } - days = uint(in) + days = int(in) } err := resp.IsDeferred().Send() if err != nil { - logger.Alert("commands/stats.go - Sending deferred", err.Error()) + s.Logger().Error("sending deferred", "error", err) return } go func() { var w io.WriterTo if v, ok := opt["user"]; ok { - w, err = statsMember(s, i, days, v.UserValue(s).ID) + w, err = statsMember(s, i, days, v.UserValue(s.UserAPI()).ID) } else { w, err = statsAll(s, i, days) } if err != nil { + s.Logger().Error("generating stats", "error", err, "guild", i.GuildID) if err = resp.IsEphemeral().SetMessage("Il y a eu une erreur...").Send(); err != nil { - logger.Alert("commands/stats.go - Sending error occurred", err.Error()) + s.Logger().Error("sending error occurred", "error", err) } return } b := new(bytes.Buffer) _, err = w.WriteTo(b) if err != nil { - logger.Alert("commands/stats.go - Writing png", err.Error()) + s.Logger().Error("writing png", "error", err) } - err = resp.AddFile(&discordgo.File{ + err = resp.AddFile(&channel.File{ Name: "plot.png", ContentType: "image/png", Reader: b, }).Send() if err != nil { - logger.Alert("commands/stats.go - Sending response", err.Error()) + s.Logger().Error("sending stats", "error", err) } }() } -func statsAll(s *discordgo.Session, i *discordgo.InteractionCreate, days uint) (io.WriterTo, error) { +func statsAll(s bot.Session, i *event.InteractionCreate, days int) (io.WriterTo, error) { return stats(s, i, days, func(before, after string) *gorm.DB { - return gokord.DB.Raw(before+"WHERE guild_id = ? and created_at > ?"+after, i.GuildID, exp.TimeStampNDaysBefore(days)) + return gokord.DB.Raw(before+"WHERE guild_id = ? and created_at > ?"+after, i.GuildID, exp.TimeStampNDaysBefore(uint(days))) }) } -func statsMember(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, discordID string) (io.WriterTo, error) { - _, err := s.GuildMember(i.GuildID, discordID) +func statsMember(s bot.Session, i *event.InteractionCreate, days int, discordID string) (io.WriterTo, error) { + _, err := s.GuildAPI().Member(i.GuildID, discordID) if err != nil { return nil, err } return stats(s, i, days, func(before, after string) *gorm.DB { return gokord.DB.Raw( before+"WHERE guild_id = ? and created_at > ? and copaing_id = ?"+after, - i.GuildID, exp.TimeStampNDaysBefore(days), user.GetCopaing(discordID, i.GuildID).ID, + i.GuildID, exp.TimeStampNDaysBefore(uint(days)), user.GetCopaing(discordID, i.GuildID).ID, ) }) } -func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, execSql func(before, after string) *gorm.DB) (io.WriterTo, error) { +func stats(s bot.Session, i *event.InteractionCreate, days int, execSql func(before, after string) *gorm.DB) (io.WriterTo, error) { var rawData []*data if gokord.Debug { var rawCopaingData []*user.CopaingXP if err := execSql("SELECT * FROM copaing_xps ", "").Scan(&rawCopaingData).Error; err != nil { - logger.Alert("commands/stats.go - Fetching result", err.Error()) + s.Logger().Error("fetching result", "error", err) return nil, err } rawData = make([]*data, len(rawCopaingData)) @@ -123,7 +138,7 @@ func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, exec if err := execSql( `SELECT "created_at"::date::text, sum("xp") as xp, "copaing_id" FROM copaing_xps `, ` GROUP BY "created_at"::date, "copaing_id"`, ).Scan(&rawDbData).Error; err != nil { - logger.Alert("commands/stats.go - Fetching result", err.Error()) + s.Logger().Error("fetching result", "error", err) return nil, err } rawData = make([]*data, len(rawDbData)) @@ -145,10 +160,10 @@ func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, exec var cp user.Copaing if err := gokord.DB.First(&cp, raw.CopaingID).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { - logger.Alert("commands/stats.go - Finding copaing", err.Error(), "id", raw.CopaingID) + s.Logger().Error("finding copaing", "error", err, "copaing", raw.CopaingID) return nil, err } - logger.Warn("Copaing not found, skipping entry", "old_id", raw.CopaingID) + s.Logger().Warn("copaing not found, skipping", "copaing", raw.CopaingID) continue } copaings[raw.CopaingID] = &cp @@ -159,7 +174,7 @@ func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, exec pts = make([]plotter.XY, days+1) for i := 0; i < len(pts); i++ { pts[i] = plotter.XY{ - X: float64(i - int(days)), + X: float64(i - days), Y: 0, } } @@ -168,6 +183,8 @@ func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, exec t := raw.CreatedAt.Unix() - now if !gokord.Debug { t = int64(math.Ceil(float64(t) / (24 * 60 * 60))) + } else { + t = int64(math.Ceil(float64(t) / exp.DebugFactor)) } pts[int64(days)+t] = plotter.XY{ // because t <= 0 X: float64(t), @@ -177,22 +194,35 @@ func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, exec return generatePlot(s, i, copaings, stats) } -func generatePlot(s *discordgo.Session, i *discordgo.InteractionCreate, copaings map[int]*user.Copaing, stats map[int][]plotter.XY) (io.WriterTo, error) { +func generatePlot(s bot.Session, i *event.InteractionCreate, copaings map[int]*user.Copaing, stats map[int][]plotter.XY) (io.WriterTo, error) { p := plot.New() - p.Title.Text = "Évolution de l'XP" + fontSizeTitle := vg.Length(16) + fontSize := vg.Length(12) + // set font size + p.Title.TextStyle.Font.Size = fontSizeTitle + p.X.Label.TextStyle.Font.Size = fontSizeTitle + p.Y.Label.TextStyle.Font.Size = fontSizeTitle + p.Legend.TextStyle.Font.Size = fontSize + // set legend style + p.Legend.YPosition = draw.PosTop + p.Legend.Top = true + p.Legend.Padding = vg.Points(2) + // set scales + p.Title.Text = "XP gagnées" p.X.Label.Text = "Jours" if gokord.Debug { - p.X.Label.Text = "Secondes" + p.X.Label.Text = fmt.Sprintf("%d secondes", exp.DebugFactor) } p.Y.Label.Text = "XP" + p.Y.Scale = exp.LevelScale{} p.Add(plotter.NewGrid()) - r := rand.New(rand.NewPCG(uint64(time.Now().Unix()), uint64(time.Now().Unix()))) + cnt := 0 for in, c := range copaings { - m, err := s.GuildMember(i.GuildID, c.DiscordID) + m, err := s.GuildAPI().Member(i.GuildID, c.DiscordID) if err != nil { - logger.Alert("commands/stats.go - Fetching guild member", err.Error()) + s.Logger().Error("fetching guild member", "error", err) return nil, err } slices.SortFunc(stats[in], func(a, b plotter.XY) int { @@ -204,21 +234,21 @@ func generatePlot(s *discordgo.Session, i *discordgo.InteractionCreate, copaings } return 0 }) - l, err := plotter.NewLine(plotter.XYs(stats[in])) + l, _, err := plotter.NewLinePoints(plotter.XYs(stats[in])) if err != nil { - logger.Alert("commands/stats.go - Adding line points", err.Error()) return nil, err } - l.LineStyle.Width = vg.Points(1) - l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)} - l.LineStyle.Color = color.RGBA{R: uint8(r.UintN(255)), G: uint8(r.UintN(255)), B: uint8(r.UintN(255)), A: 255} + l.Color = colors[cnt%len(colors)] + if len(copaings) < 4 { + l.Width = vg.Points(2) + } + if cnt/len(colors) > 0 { + size := 7 / min(cnt/len(colors), 7) + l.Dashes = []vg.Length{vg.Points(float64(size)), vg.Points(float64(size))} + } p.Add(l) p.Legend.Add(m.DisplayName(), l) + cnt++ } - w, err := p.WriterTo(8*vg.Inch, 6*vg.Inch, "png") - if err != nil { - logger.Alert("commands/stats.go - Generating png", err.Error()) - return nil, err - } - return w, nil + return p.WriterTo(12*vg.Inch, 8*vg.Inch, "png") } |
