2022-09-10 11:39:07 +00:00
|
|
|
package punish
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2022-10-22 22:08:02 +00:00
|
|
|
"gorm.io/gorm"
|
|
|
|
"gorm.io/gorm/clause"
|
|
|
|
|
2023-11-27 23:09:27 +00:00
|
|
|
"github.com/Luzifer/go_helpers/v2/backoff"
|
|
|
|
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
|
2022-11-02 21:38:14 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
type (
|
|
|
|
punishLevel struct {
|
|
|
|
Key string `gorm:"primaryKey"`
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
LastLevel int
|
|
|
|
Executed time.Time
|
|
|
|
Cooldown time.Duration
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
2022-10-22 22:08:02 +00:00
|
|
|
)
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func calculateCurrentPunishments(db database.Connector) (err error) {
|
|
|
|
var ps []punishLevel
|
2023-11-27 23:09:27 +00:00
|
|
|
if err = helpers.Retry(func() error { return db.DB().Find(&ps).Error }); err != nil {
|
2022-10-22 22:08:02 +00:00
|
|
|
return errors.Wrap(err, "querying punish_levels")
|
|
|
|
}
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
for _, p := range ps {
|
2022-09-10 11:39:07 +00:00
|
|
|
var (
|
|
|
|
actUpdate bool
|
2022-10-22 22:08:02 +00:00
|
|
|
lvl = &levelConfig{
|
|
|
|
LastLevel: p.LastLevel,
|
|
|
|
Executed: p.Executed,
|
|
|
|
Cooldown: p.Cooldown,
|
|
|
|
}
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
for {
|
|
|
|
cooldownTime := lvl.Executed.Add(lvl.Cooldown)
|
2022-10-22 22:08:02 +00:00
|
|
|
if cooldownTime.After(time.Now().UTC()) {
|
2022-09-10 11:39:07 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
lvl.Executed = cooldownTime
|
|
|
|
lvl.LastLevel--
|
|
|
|
actUpdate = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Level 0 is the first punishment level, so only remove if it drops below 0
|
|
|
|
if lvl.LastLevel < 0 {
|
2022-10-22 22:08:02 +00:00
|
|
|
if err = deletePunishmentForKey(db, p.Key); err != nil {
|
2022-09-10 11:39:07 +00:00
|
|
|
return errors.Wrap(err, "cleaning up expired punishment")
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if actUpdate {
|
2022-10-22 22:08:02 +00:00
|
|
|
if err = setPunishmentForKey(db, p.Key, lvl); err != nil {
|
2022-09-10 11:39:07 +00:00
|
|
|
return errors.Wrap(err, "updating punishment")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
return nil
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func deletePunishment(db database.Connector, channel, user, uuid string) error {
|
|
|
|
return deletePunishmentForKey(db, getDBKey(channel, user, uuid))
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func deletePunishmentForKey(db database.Connector, key string) error {
|
|
|
|
return errors.Wrap(
|
2023-11-27 23:09:27 +00:00
|
|
|
helpers.RetryTransaction(db.DB(), func(tx *gorm.DB) error {
|
|
|
|
return tx.Delete(&punishLevel{}, "key = ?", key).Error
|
|
|
|
}),
|
2022-10-22 22:08:02 +00:00
|
|
|
"deleting punishment info",
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func getPunishment(db database.Connector, channel, user, uuid string) (*levelConfig, error) {
|
|
|
|
if err := calculateCurrentPunishments(db); err != nil {
|
2022-09-10 11:39:07 +00:00
|
|
|
return nil, errors.Wrap(err, "updating punishment states")
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
var (
|
|
|
|
lc = &levelConfig{LastLevel: -1}
|
|
|
|
p punishLevel
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
2023-11-27 23:09:27 +00:00
|
|
|
err := helpers.Retry(func() error {
|
|
|
|
err := db.DB().First(&p, "key = ?", getDBKey(channel, user, uuid)).Error
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
2024-03-27 23:15:14 +00:00
|
|
|
return backoff.NewErrCannotRetry(err)
|
2023-11-27 23:09:27 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
})
|
2022-09-10 11:39:07 +00:00
|
|
|
switch {
|
|
|
|
case err == nil:
|
2022-10-22 22:08:02 +00:00
|
|
|
return &levelConfig{
|
|
|
|
LastLevel: p.LastLevel,
|
|
|
|
Executed: p.Executed,
|
|
|
|
Cooldown: p.Cooldown,
|
|
|
|
}, nil
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
2022-09-10 11:39:07 +00:00
|
|
|
return lc, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.Wrap(err, "getting punishment from database")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func setPunishment(db database.Connector, channel, user, uuid string, lc *levelConfig) error {
|
|
|
|
return setPunishmentForKey(db, getDBKey(channel, user, uuid), lc)
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func setPunishmentForKey(db database.Connector, key string, lc *levelConfig) error {
|
|
|
|
if lc == nil {
|
|
|
|
return errors.New("nil levelConfig given")
|
|
|
|
}
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
return errors.Wrap(
|
2023-11-27 23:09:27 +00:00
|
|
|
helpers.RetryTransaction(db.DB(), func(tx *gorm.DB) error {
|
|
|
|
return tx.Clauses(clause.OnConflict{
|
|
|
|
Columns: []clause.Column{{Name: "key"}},
|
|
|
|
UpdateAll: true,
|
|
|
|
}).Create(punishLevel{
|
|
|
|
Key: key,
|
|
|
|
LastLevel: lc.LastLevel,
|
|
|
|
Executed: lc.Executed,
|
|
|
|
Cooldown: lc.Cooldown,
|
|
|
|
}).Error
|
|
|
|
}),
|
2022-10-22 22:08:02 +00:00
|
|
|
"updating punishment info",
|
|
|
|
)
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getDBKey(channel, user, uuid string) string {
|
|
|
|
return strings.Join([]string{channel, user, uuid}, "::")
|
|
|
|
}
|