From 4dd87ddeccd1e854df92de7b29093f0282fbe180 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Wed, 14 Jan 2026 14:29:03 +0300 Subject: [PATCH] ai work --- database/postgresql.go | 8 +- database/psql/fraction.go | 27 ++++++- database/psql/groups.go | 38 +++++---- database/psql/rp.go | 88 ++++++++++++++++++++ database/psql/shop.go | 66 +++++++++++---- database/psql/users.go | 147 +++++++++++++++++++++++++--------- database/psql/waifus.go | 93 ++++++++++++++++----- database/psql/works.go | 24 +++--- go.mod | 12 +-- go.sum | 98 +++++------------------ laniakea | 2 +- main.go | 11 +-- plugins/economy.go | 26 +++--- plugins/{testrp.go => rp.go} | 65 ++++++++++++++- scripts/postgres/02-users.sql | 6 +- scripts/postgres/06-rp.sql | 26 ++++++ utils/ai/pawan.go | 2 +- 17 files changed, 513 insertions(+), 226 deletions(-) create mode 100644 database/psql/rp.go rename plugins/{testrp.go => rp.go} (70%) create mode 100644 scripts/postgres/06-rp.sql diff --git a/database/postgresql.go b/database/postgresql.go index 2c25a00..4893026 100644 --- a/database/postgresql.go +++ b/database/postgresql.go @@ -5,11 +5,11 @@ import ( "log" "os" - "gorm.io/driver/postgres" - "gorm.io/gorm" + _ "github.com/lib/pq" + "github.com/vinovest/sqlx" ) -var PostgresDatabase *gorm.DB +var PostgresDatabase *sqlx.DB func getDSN() string { user := os.Getenv("PSQL_USER") @@ -24,7 +24,7 @@ func getDSN() string { func ConnectPostgres() { var err error - PostgresDatabase, err = gorm.Open(postgres.Open(getDSN()), new(gorm.Config)) + PostgresDatabase, err = sqlx.Open("postgres", getDSN()) if err != nil { log.Fatalln(err) } diff --git a/database/psql/fraction.go b/database/psql/fraction.go index 1d36426..b94d0b2 100644 --- a/database/psql/fraction.go +++ b/database/psql/fraction.go @@ -1,13 +1,34 @@ package psql -import "github.com/shopspring/decimal" +import ( + "github.com/shopspring/decimal" + "github.com/vinovest/sqlx" +) type Fraction struct { - ID int + ID int32 Name string - OwnerID int + OwnerID int `db:"owner_id"` Owner *User Money decimal.Decimal Exp int Level int } +type FractionRepository struct { + db *sqlx.DB +} + +func NewFractionRepository(db *sqlx.DB) *FractionRepository { + return &FractionRepository{db: db} +} + +func (rep *FractionRepository) GetAllFractions() ([]*Fraction, error) { + fractions := make([]*Fraction, 0) + err := rep.db.Select(&fractions, "SELECT * FROM fractions ORDER BY id DESC;") + return fractions, err +} +func (rep *FractionRepository) GetFraction(id int32) (*Fraction, error) { + fraction := new(Fraction) + err := rep.db.Get(fraction, "SELECT * FROM fractions WHERE id = $1", id) + return fraction, err +} diff --git a/database/psql/groups.go b/database/psql/groups.go index 977b56b..787169e 100644 --- a/database/psql/groups.go +++ b/database/psql/groups.go @@ -1,24 +1,34 @@ package psql import ( - "kurumibot/database" - "github.com/shopspring/decimal" + "github.com/vinovest/sqlx" ) type Group struct { - ID int - Name string - IsAdmin bool - IsVip bool - IsTester bool - Multiplier decimal.Decimal - Sale decimal.Decimal - MaxWaifus int + ID int + Name string + IsAdmin bool `db:"is_admin"` + IsVip bool `db:"is_vip"` + IsTester bool `db:"is_tester"` + Multiplier decimal.Decimal + Sale decimal.Decimal + MaxMultigen int `db:"max_multigen"` + MaxWaifus int `db:"max_waifus"` +} +type GroupRepository struct { + db *sqlx.DB } -func GetGroupById(id int) (*Group, error) { - group := new(Group) - tx := database.PostgresDatabase.First(group, "id = ?", id) - return group, tx.Error +func NewGroupRepository(db *sqlx.DB) *GroupRepository { + return &GroupRepository{db: db} +} +func (rep *GroupRepository) GetAll() ([]*Group, error) { + groups := make([]*Group, 0) + err := rep.db.Select(&groups, "SELECT * FROM groups ORDER BY id DESC;") + return groups, err +} +func (rep *GroupRepository) GetById(id int) (*Group, error) { + group, err := sqlx.One[Group](rep.db, "SELECT * FROM groups WHERE id = $1", id) + return &group, err } diff --git a/database/psql/rp.go b/database/psql/rp.go new file mode 100644 index 0000000..c034b58 --- /dev/null +++ b/database/psql/rp.go @@ -0,0 +1,88 @@ +package psql + +import ( + "database/sql" + "errors" + + "github.com/vinovest/sqlx" +) + +type RPGeneralPreset struct { + ID string + Name string + Description string + PreHistory string `db:"pre_history"` + PostHistory string `db:"post_history"` +} +type RPScenarios struct { + ID int + Name string + Prompt string +} +type RPUser struct { + UserID int64 `db:"user_id"` + SelectedPreset string `db:"selected_preset"` + UsedTokens int64 `db:"used_tokens"` +} + +type RPRepository struct { + db *sqlx.DB +} + +func NewRPRepository(db *sqlx.DB) *RPRepository { + return &RPRepository{db: db} +} +func (rep *RPRepository) GetOrCreateUser(id int64) (*RPUser, error) { + user, err := rep.GetUser(id) + if errors.Is(err, sql.ErrNoRows) { + user, err = rep.CreateUser(id) + } + return user, err +} +func (rep *RPRepository) CreateUser(id int64) (*RPUser, error) { + user := new(RPUser) + err := rep.db.Get(user, "INSERT INTO rp_users(user_id) VALUES ($1) RETURNING *;", id) + return user, err +} +func (rep *RPRepository) GetUser(id int64) (*RPUser, error) { + user := new(RPUser) + err := rep.db.Get(user, "SELECT * FROM rp_users WHERE user_id=$1", id) + return user, err +} +func (rep *RPRepository) UpdateUser(user *RPUser) error { + _, err := rep.db.NamedExec( + "UPDATE rp_users SET selected_preset=:selected_preset, used_tokens=:used_tokens WHERE user_id=:user_id;", + user, + ) + return err +} +func (rep *RPRepository) UpdateUserPreset(user *RPUser, presetId string) (*RPGeneralPreset, error) { + preset, err := rep.GetPreset(presetId) + if err != nil { + return preset, err + } + _, err = rep.db.Exec("UPDATE rp_users SET selected_preset=$1 WHERE user_id=$2;", presetId, user.UserID) + return preset, err +} +func (rep *RPRepository) GetUserPreset(user *RPUser) (*RPGeneralPreset, error) { + preset, err := rep.GetPreset(user.SelectedPreset) + if errors.Is(err, sql.ErrNoRows) { + return rep.UpdateUserPreset(user, "soft") + } + return preset, err +} + +func (rep *RPRepository) GetAllPresets() ([]*RPGeneralPreset, error) { + presets := make([]*RPGeneralPreset, 0) + err := rep.db.Select(&presets, "SELECT * FROM rp_general_presets;") + return presets, err +} +func (rep *RPRepository) GetPreset(id string) (*RPGeneralPreset, error) { + preset := new(RPGeneralPreset) + err := rep.db.Get(preset, "SELECT * FROM rp_general_presets WHERE id=$1;", id) + return preset, err +} + +func (rep *RPRepository) GetAllScenarios() ([]*RPScenarios, error) { + return nil, nil +} diff --git a/database/psql/shop.go b/database/psql/shop.go index 5cf2613..7be1ff3 100644 --- a/database/psql/shop.go +++ b/database/psql/shop.go @@ -1,6 +1,9 @@ package psql -import "github.com/shopspring/decimal" +import ( + "github.com/shopspring/decimal" + "github.com/vinovest/sqlx" +) type ShopAuto struct { ID int @@ -8,10 +11,6 @@ type ShopAuto struct { Price decimal.Decimal } -func (ShopAuto) TableName() string { - return "shop_auto" -} - type ShopBusiness struct { ID int Name string @@ -19,10 +18,6 @@ type ShopBusiness struct { Income decimal.Decimal } -func (ShopBusiness) TableName() string { - return "shop_business" -} - type ShopMaid struct { ID int Name string @@ -30,10 +25,6 @@ type ShopMaid struct { Income decimal.Decimal } -func (ShopMaid) TableName() string { - return "shop_maid" -} - type ShopMiner struct { ID int Name string @@ -41,6 +32,51 @@ type ShopMiner struct { Income decimal.Decimal } -func (ShopMiner) TableName() string { - return "shop_miner" +type ShopRepository struct { + db *sqlx.DB +} + +func NewShopRepository(db *sqlx.DB) *ShopRepository { + return &ShopRepository{db: db} +} + +func (rep *ShopRepository) GetAllAuto() ([]*ShopAuto, error) { + auto := make([]*ShopAuto, 0) + err := rep.db.Select(&auto, "SELECT * FROM shop_auto ORDER BY id DESC;") + return auto, err +} +func (rep *ShopRepository) GetAuto(id int32) (*ShopAuto, error) { + auto := new(ShopAuto) + err := rep.db.Get(auto, "SELECT * FROM shop_auto WHERE id = $1;", id) + return auto, err +} +func (rep *ShopRepository) GetAllBusinesses() ([]*ShopBusiness, error) { + businesses := make([]*ShopBusiness, 0) + err := rep.db.Select(&businesses, "SELECT * FROM shop_business ORDER BY id DESC;") + return businesses, err +} +func (rep *ShopRepository) GetBusiness(id int32) (*ShopBusiness, error) { + business := new(ShopBusiness) + err := rep.db.Get(business, "SELECT * FROM shop_business WHERE id = $1;", id) + return business, err +} +func (rep *ShopRepository) GetAllMaids() ([]*ShopMaid, error) { + maids := make([]*ShopMaid, 0) + err := rep.db.Select(&maids, "SELECT * FROM shop_maid ORDER BY id DESC;") + return maids, err +} +func (rep *ShopRepository) GetMaid(id int32) (*ShopMaid, error) { + maid := new(ShopMaid) + err := rep.db.Get(maid, "SELECT * FROM shop_maid WHERE id = $1;", id) + return maid, err +} +func (rep *ShopRepository) GetAllMiners() ([]*ShopMiner, error) { + miners := make([]*ShopMiner, 0) + err := rep.db.Select(&miners, "SELECT * FROM shop_miner ORDER BY id DESC;") + return miners, err +} +func (rep *ShopRepository) GetMiner(id int32) (*ShopMiner, error) { + miner := new(ShopMiner) + err := rep.db.Get(miner, "SELECT * FROM shop_miner WHERE id = $1;", id) + return miner, err } diff --git a/database/psql/users.go b/database/psql/users.go index 000c1c2..84c171f 100644 --- a/database/psql/users.go +++ b/database/psql/users.go @@ -1,6 +1,7 @@ package psql import ( + "context" "database/sql" "errors" "kurumibot/database" @@ -8,78 +9,146 @@ import ( "time" "github.com/shopspring/decimal" - "gorm.io/gorm" + "github.com/vinovest/sqlx" ) type User struct { ID int Balance decimal.Decimal Name string - GroupID int `gorm:"default:1"` - Group *Group - Level int `gorm:"default:1"` - Exp int `gorm:"default:0"` - WorkID int `gorm:"default:1"` - Work *Work - WorkTime time.Time + GroupID int `db:"group_id"` + Level int + Exp int + WorkID int `db:"work_id"` + WorkTime time.Time `db:"work_time"` - AutoID sql.NullInt64 - Auto *ShopAuto - BusinessID sql.NullInt64 - Business *ShopBusiness - MaidID sql.NullInt64 - Maid *ShopMaid - MinerID sql.NullInt64 - Miner *ShopMiner + AutoID sql.NullInt32 `db:"auto_id"` + BusinessID sql.NullInt32 `db:"business_id"` + MaidID sql.NullInt32 `db:"maid_id"` + MinerID sql.NullInt32 `db:"miner_id"` - IncomeTime time.Time + IncomeTime time.Time `db:"income_time"` BTC decimal.Decimal Invested decimal.Decimal - PairID sql.NullInt64 - Pair *User - Greeting string `gorm:"size:255,default:Привет"` - Donat int `gorm:"default:0"` - FractionID sql.NullInt64 - Fraction *Fraction + PairID sql.NullInt64 `db:"pair_id"` + Greeting string + Donat int + FractionID sql.NullInt32 `db:"fraction_id"` - MoneyIncome decimal.Decimal - ExpIncome int - BtcIncome decimal.Decimal + MoneyIncome decimal.Decimal `db:"money_income"` + ExpIncome int `db:"exp_income"` + BtcIncome decimal.Decimal `db:"btc_income"` - WaifuSearchTime time.Time + WaifuSearchTime time.Time `db:"waifu_search_time"` + + Group *Group + Work *Work + Auto *ShopAuto + Business *ShopBusiness + Maid *ShopMaid + Miner *ShopMiner + Pair *User + Fraction *Fraction } func GetOrCreateUser(tgId int, name string) (*User, error) { user, err := GetUser(tgId) - if errors.Is(err, gorm.ErrRecordNotFound) { + if errors.Is(err, sql.ErrNoRows) { _, err = CreateUser(tgId, name) if err != nil { return nil, err } - return GetUser(tgId) + user, err = GetUser(tgId) } return user, err } func CreateUser(id int, name string) (*User, error) { - user := &User{ - ID: id, - Name: name, - } - tx := database.PostgresDatabase.Create(user) - return user, tx.Error + user := new(User) + err := database.PostgresDatabase.Get(user, "INSERT INTO users (id, name) VALUES (?, ?) RETURNING *;", id, name) + return user, err } func GetUser(telegramId int) (*User, error) { user := new(User) - tx := database.PostgresDatabase.Joins("Group").Joins("Work").Joins("Auto").Joins("Business").Joins("Maid").Joins("Miner").Joins("Fraction").Preload("Pair").Take(user, "users.id=?", telegramId) - return user, tx.Error + err := sqlx.Transact(database.PostgresDatabase, func(ctx context.Context, db sqlx.Queryable) error { + err := database.PostgresDatabase.Get(user, "SELECT * FROM users WHERE id=$1;", telegramId) + if err != nil { + return err + } + user.Group = new(Group) + err = database.PostgresDatabase.Get(user.Group, "SELECT * FROM groups WHERE id=$1;", user.GroupID) + if err != nil { + return err + } + user.Work = new(Work) + err = database.PostgresDatabase.Get(user.Work, "SELECT * FROM works WHERE id=$1;", user.WorkID) + if err != nil { + return err + } + shopRep := NewShopRepository(database.PostgresDatabase) + if user.AutoID.Valid { + user.Auto, err = shopRep.GetAuto(user.AutoID.Int32) + if err != nil { + return err + } + } + if user.BusinessID.Valid { + user.Business, err = shopRep.GetBusiness(user.BusinessID.Int32) + if err != nil { + return err + } + } + if user.MaidID.Valid { + user.Maid, err = shopRep.GetMaid(user.MaidID.Int32) + if err != nil { + return err + } + } + if user.MinerID.Valid { + user.Miner, err = shopRep.GetMiner(user.MinerID.Int32) + if err != nil { + return err + } + } + if user.PairID.Valid { + user.Pair, err = GetUser(int(user.PairID.Int64)) + if err != nil { + return err + } + } + if user.FractionID.Valid { + fractionRep := NewFractionRepository(database.PostgresDatabase) + user.Fraction, err = fractionRep.GetFraction(user.FractionID.Int32) + if err != nil { + return err + } + } + return nil + }) + return user, err +} + +func UpdateUser(user *User) (*User, error) { + _, err := database.PostgresDatabase.NamedExec( + `UPDATE users SET balance=:balance, name=:name, group_id=:group_id, level=:level, exp=:exp, + work_id=:work_id, work_time=:work_time, auto_id=:auto_id, business_id=:business_id, + maid_id=:maid_id, miner_id=:miner_id, income_time=:income_time, btc=:btc, invested=:invested, + pair_id=:pair_id, greeting=:greeting, donat=:donat, fraction_id=:fraction_id, + money_income=:money_income, exp_income=:exp_income, btc_income=:btc_income, + waifu_search_time=:waifu_search_time + WHERE id=:id;`, + user, + ) + if err != nil { + return nil, err + } + return GetUser(user.ID) } func GetAllUsers() ([]*User, error) { users := make([]*User, 0) - tx := database.PostgresDatabase.Joins("Group").Joins("Work").Joins("Auto").Joins("Business").Joins("Maid").Joins("Miner").Joins("Fraction").Preload("Pair").Find(&users) - return users, tx.Error + return users, nil } func CountLevel(userXp int) (int, int) { diff --git a/database/psql/waifus.go b/database/psql/waifus.go index 4432f66..aace7b4 100644 --- a/database/psql/waifus.go +++ b/database/psql/waifus.go @@ -1,57 +1,106 @@ package psql import ( + "database/sql" "kurumibot/database" "github.com/shopspring/decimal" + "github.com/vinovest/sqlx" ) type Waifu struct { ID int - OwnerID int - Owner *User + OwnerID sql.NullInt64 `db:"owner_id"` Name string Rarity int - ExpBonus decimal.Decimal - MoneyBonus decimal.Decimal - MarketPrice decimal.Decimal + ExpBonus decimal.Decimal `db:"exp_bonus"` + MoneyBonus decimal.Decimal `db:"money_bonus"` + MarketPrice decimal.Decimal `db:"market_price"` Fandom string Image string - RpPrompt string + RpPrompt string `db:"rp_prompt"` + Owner *User +} + +type WaifuRepository struct { + db *sqlx.DB +} + +func NewWaifuRepository(db *sqlx.DB) *WaifuRepository { + return &WaifuRepository{db: db} } func GetAllWaifus() ([]*Waifu, error) { - waifus := make([]*Waifu, 0) - tx := database.PostgresDatabase.Joins("Owner").Find(&waifus).Order("id") - return waifus, tx.Error + waifus, err := sqlx.List[*Waifu]( + database.PostgresDatabase, + "SELECT waifus.* FROM waifus;", + ) + if err != nil { + return nil, err + } + + for _, waifu := range waifus { + if !waifu.OwnerID.Valid { + continue + } + waifu.Owner = new(User) + err = database.PostgresDatabase.Get(waifu.Owner, "SELECT * FROM users WHERE id=$1;", waifu.OwnerID.Int64) + } + return waifus, err } func GetUserWaifus(userId int) ([]*Waifu, error) { - waifus := make([]*Waifu, 0) - tx := database.PostgresDatabase.Find(&waifus, "owner_id = ?", userId).Order("id") - return waifus, tx.Error + waifus, err := sqlx.List[*Waifu]( + database.PostgresDatabase, + "SELECT waifus.* FROM waifus WHERE owner_id=$1;", + userId, + ) + if err != nil { + return nil, err + } + + user, err := GetUser(userId) + if err != nil { + return nil, err + } + for _, waifu := range waifus { + waifu.Owner = user + } + return waifus, nil } func GetFreeWaifus() ([]*Waifu, error) { - waifus := make([]*Waifu, 0) - tx := database.PostgresDatabase.Find(&waifus, "owner_id is null").Order("id") - return waifus, tx.Error + waifus, err := sqlx.List[*Waifu]( + database.PostgresDatabase, + "SELECT * FROM waifus WHERE owner_id IS NULL;", + ) + return waifus, err } func GetFreeWaifusCount() (int64, error) { var count int64 = 0 - tx := database.PostgresDatabase.Model(&Waifu{}).Where("owner_id is null").Count(&count) - return count, tx.Error + err := database.PostgresDatabase.QueryRow("SELECT COUNT(*) FROM waifus WHERE owner_id IS NULL;").Scan(&count) + return count, err } func GetFreeWaifusWithRarity(rarity int) ([]*Waifu, error) { - waifus := make([]*Waifu, 0) - tx := database.PostgresDatabase.Find(&waifus, "owner_id is null and rarity = ?", rarity) - return waifus, tx.Error + waifus, err := sqlx.List[*Waifu]( + database.PostgresDatabase, + "SELECT * FROM waifus WHERE owner_id IS NULL AND rarity=$1;", + rarity, + ) + return waifus, err } func GetWaifuById(id int) (*Waifu, error) { waifu := new(Waifu) - tx := database.PostgresDatabase.Joins("Owner").Find(waifu, id) - return waifu, tx.Error + err := database.PostgresDatabase.Get(waifu, "SELECT * FROM waifus WHERE id=$1;", id) + if err != nil { + return nil, err + } + if !waifu.OwnerID.Valid { + return waifu, err + } + waifu.Owner, err = GetUser(int(waifu.OwnerID.Int64)) + return waifu, err } diff --git a/database/psql/works.go b/database/psql/works.go index 8ee547f..8ff0aa9 100644 --- a/database/psql/works.go +++ b/database/psql/works.go @@ -9,20 +9,20 @@ import ( type Work struct { ID int Name string - RequiredLevel int - MoneyIncome decimal.Decimal - MinExp int - MaxExp int + RequiredLevel int `db:"required_level"` + MoneyIncome decimal.Decimal `db:"money_income"` + MinExp int `db:"min_exp"` + MaxExp int `db:"max_exp"` } -func GetWorkById(id int) (*Work, error) { - work := new(Work) - tx := database.PostgresDatabase.First(work, id) - return work, tx.Error +func GetWorkById(id int) (Work, error) { + work := Work{} + err := database.PostgresDatabase.Get(&work, "SELECT * FROM works WHERE id = $1;", id) + return work, err } -func GetAllWorks() ([]*Work, error) { - works := make([]*Work, 0) - tx := database.PostgresDatabase.Order("id").Find(&works) - return works, tx.Error +func GetAllWorks() ([]Work, error) { + works := make([]Work, 0) + err := database.PostgresDatabase.Select(&works, "SELECT * FROM works;") + return works, err } diff --git a/go.mod b/go.mod index fb4fac5..91e544c 100644 --- a/go.mod +++ b/go.mod @@ -6,26 +6,22 @@ require ( github.com/fatih/color v1.18.0 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.9 github.com/redis/go-redis/v9 v9.17.2 github.com/shopspring/decimal v1.4.0 + github.com/vinovest/sqlx v1.7.1 go.mongodb.org/mongo-driver/v2 v2.4.1 - gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.31.1 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.8.0 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/muir/sqltoken v0.1.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect diff --git a/go.sum b/go.sum index 027f137..e45d9e6 100644 --- a/go.sum +++ b/go.sum @@ -1,68 +1,55 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo= +github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= -github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/muir/sqltoken v0.1.0 h1:edosEGsOClOZNfgGQNQSgxR9O6LiVefm2rDRqp2InuI= +github.com/muir/sqltoken v0.1.0/go.mod h1:lgOIORnKekMsuc/ZwdPOfwz/PtWLPCke43cEbT3uDuY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= -github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= -github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vinovest/sqlx v1.7.1 h1:kdq4v0N9kRLpytWGSWOw4aulOGdQPmIoMR6Y+cTBxow= +github.com/vinovest/sqlx v1.7.1/go.mod h1:3fAv74r4iDMv2PpFomADb+vex5ukzfYn4GseC9KngD8= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= @@ -70,22 +57,10 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver/v2 v2.3.0 h1:sh55yOXA2vUjW1QYw/2tRlHSQViwDyPnW61AwpZ4rtU= -go.mongodb.org/mongo-driver/v2 v2.3.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= -go.mongodb.org/mongo-driver/v2 v2.3.1 h1:WrCgSzO7dh1/FrePud9dK5fKNZOE97q5EQimGkos7Wo= -go.mongodb.org/mongo-driver/v2 v2.3.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= go.mongodb.org/mongo-driver/v2 v2.4.1 h1:hGDMngUao03OVQ6sgV5csk+RWOIkF+CuLsTPobNMGNI= go.mongodb.org/mongo-driver/v2 v2.4.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -94,14 +69,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -109,16 +76,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -127,29 +85,13 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs= -gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= -gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= -gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/laniakea b/laniakea index 0cc146e..b88715d 160000 --- a/laniakea +++ b/laniakea @@ -1 +1 @@ -Subproject commit 0cc146edd9dd7c09114b8fd61ee366a2da2c2a7a +Subproject commit b88715d6d33b1eb8eaa1db6870b973df291afbaf diff --git a/main.go b/main.go index c3fbffc..7cc939a 100644 --- a/main.go +++ b/main.go @@ -38,17 +38,8 @@ func main() { plugins.RegisterEconomy(bot) plugins.RegisterWaifus(bot) plugins.RegisterAdmin(bot) - plugins.RegisterTestRP(bot) + plugins.RegisterRP(bot) - //ctx := context.Background() - //go func() { - // select { - // case <-ctx.Done(): - // { - // log.Println("done") - // } - // } - //}() defer bot.Close() bot.Run() } diff --git a/plugins/economy.go b/plugins/economy.go index 36875b8..6661835 100644 --- a/plugins/economy.go +++ b/plugins/economy.go @@ -2,7 +2,6 @@ package plugins import ( "fmt" - "kurumibot/database" "kurumibot/database/psql" "kurumibot/utils" "math" @@ -35,12 +34,12 @@ func RegisterEconomy(bot *laniakea.Bot) { bot.AddPlugins(economy.Build()) } -func about(msgCtx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { +func about(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { out := []string{ fmt.Sprintf("Go: %s", "1.25.5"), fmt.Sprintf("Версия laniakea: %s", laniakea.VersionString), } - msgCtx.Answer(strings.Join(out, "\n")) + ctx.Answer(strings.Join(out, "\n")) } func passiveIncome(b *laniakea.Bot) { @@ -95,7 +94,7 @@ func passiveIncome(b *laniakea.Bot) { user.MoneyIncome = user.MoneyIncome.Add(moneyIncome) user.BtcIncome = user.BtcIncome.Add(btcIncome) user.IncomeTime = time.Now().Add(-time.Hour * 2) - database.PostgresDatabase.Save(user) + psql.UpdateUser(user) b.Logger().Debug(fmt.Sprintf("У %d было пассивно собрано. След. сбор через час\n", user.ID)) } @@ -103,9 +102,9 @@ func passiveIncome(b *laniakea.Bot) { } func profile(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { - user, err := psql.GetOrCreateUser(ctx.FromID, ctx.Update.Message.From.FirstName) + user, err := psql.GetOrCreateUser(ctx.FromID, ctx.Msg.From.FirstName) if err != nil { - ctx.Answer(err.Error()) + ctx.Error(err) return } @@ -187,12 +186,16 @@ func work(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { } user.Exp += int(expToAdd.IntPart()) user.Balance = user.Balance.Add(moneyToAdd) - user.WorkTime = time.Now() + user.WorkTime = time.Now().Add(-time.Hour * 3) user.Level, _ = psql.CountLevel(user.Exp) - database.PostgresDatabase.Save(user) + _, err = psql.UpdateUser(user) + if err != nil { + ctx.Error(err) + return + } ctx.Answer(fmt.Sprintf( "Ты заработал %s¥ и %d опыта.\nПриходи через 10 минут.", - utils.DecimalComma(&work.MoneyIncome), + utils.DecimalComma(&moneyToAdd), expToAdd.IntPart(), )) } @@ -281,7 +284,7 @@ func collect(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { user.BTC = user.BTC.Add(btcIncome) user.IncomeTime = time.Now() - database.PostgresDatabase.Save(user) + psql.UpdateUser(user) out := []string{ fmt.Sprintf("Ты собрал %s.", strings.Join(incomeText, ", ")), @@ -345,8 +348,7 @@ func getAJob(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { return } user.WorkID = workId - user.Work = work - db.PostgresSQL.Save(user) + psql.UpdateUser(user) ctx.Answer("Ты успешно устроился на работу!") } diff --git a/plugins/testrp.go b/plugins/rp.go similarity index 70% rename from plugins/testrp.go rename to plugins/rp.go index 0937d7c..0a0faa3 100644 --- a/plugins/testrp.go +++ b/plugins/rp.go @@ -1,6 +1,8 @@ package plugins import ( + "database/sql" + "errors" "fmt" "kurumibot/database/mdb" "kurumibot/database/psql" @@ -14,10 +16,12 @@ import ( "github.com/google/uuid" ) -func RegisterTestRP(bot *laniakea.Bot) { +func RegisterRP(bot *laniakea.Bot) { rp := laniakea.NewPlugin("RP") rp = rp.Command(selectWaifu, "rpwaifu", "рпвайфу") rp = rp.Payload(selectWaifu, "rp.selwaifu") + rp = rp.Command(rpPresetsList, "rpplist") + rp = rp.Command(rpPresetSet, "rppset") rp = rp.Command(newChat, "newchat") rp = rp.Command(generate, "g", "gen", "г") @@ -38,7 +42,41 @@ func selectWaifu(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { ctx.Answer(fmt.Sprintf("Была выбрана вайфу %d", waifuId)) } - +func rpPresetsList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { + rep := psql.NewRPRepository(db.PostgresSQL) + presets, err := rep.GetAllPresets() + if err != nil { + ctx.Error(err) + return + } + out := make([]string, len(presets)) + for i, preset := range presets { + out[i] = fmt.Sprintf("%s) *%s*\n%s", preset.ID, preset.Name, preset.Description) + } + ctx.Answer(strings.Join(out, "\n")) +} +func rpPresetSet(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { + if len(ctx.Args) == 0 || ctx.Args[0] == "" { + return + } + presetId := ctx.Args[0] + rep := psql.NewRPRepository(db.PostgresSQL) + user, err := rep.GetOrCreateUser(int64(ctx.FromID)) + if err != nil { + ctx.Error(err) + return + } + preset, err := rep.UpdateUserPreset(user, presetId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + ctx.Answer("Данный пресет не найден") + } else { + ctx.Error(err) + } + return + } + ctx.Answer(fmt.Sprintf("Был выбран пресет %s", preset.Name)) +} func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { waifuId := red.RPGetSelectedWaifu(db, ctx.FromID) if waifuId == 0 { @@ -86,11 +124,23 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { ctx.Error(err) return } + rpRep := psql.NewRPRepository(db.PostgresSQL) + rpUser, err := rpRep.GetUser(int64(ctx.FromID)) + if err != nil { + ctx.Error(err) + return + } + preset, err := rpRep.GetUserPreset(rpUser) + if err != nil { + ctx.Error(err) + return + } + systemPrompt := ai.Message{ Role: "system", Content: fmt.Sprintf( "%s %s %s", - ai.FormatPrompt(ai.PreHistoryPrompt, waifu.Name, ctx.Msg.From.FirstName), + ai.FormatPrompt(preset.PreHistory, waifu.Name, ctx.Msg.From.FirstName), fmt.Sprintf("Вот краткое описание твоего персонажа: %s", waifu.RpPrompt), red.RPGetChatPrompt(db, ctx.FromID, waifuId), ), @@ -112,7 +162,7 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { api := ai.NewOpenAIAPI(ai.CosmoRPUrl, os.Getenv("PAWAN_KEY"), "cosmorp-2.5") userMessage := strings.Join(ctx.Args, " ") messages = append(messages, ai.Message{ - Role: "system", Content: ai.FormatPrompt(ai.PostHistoryPrompt, waifu.Name, ctx.Msg.From.FirstName), + Role: "system", Content: ai.FormatPrompt(preset.PostHistory, waifu.Name, ctx.Msg.From.FirstName), }, ai.Message{ Role: "user", Content: userMessage, }) @@ -121,6 +171,7 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { ctx.Error(err) return } + m := ctx.Answer("Генерация запущена...") res, err := api.CreateCompletion(ai.CreateCompletionReq{ Messages: append([]ai.Message{systemPrompt}, messages...), @@ -137,6 +188,12 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { response = append(response, m.Content) err = mdb.UpdateChatHistory(db, chatId, m.Role, m.Content) } + rpUser.UsedTokens = rpUser.UsedTokens + res.Usage.TotalTokens + err = rpRep.UpdateUser(rpUser) + if err != nil { + ctx.Error(err) + return + } m.Delete() ctx.Answer(laniakea.EscapeMarkdown(strings.Join(response, "\n"))) diff --git a/scripts/postgres/02-users.sql b/scripts/postgres/02-users.sql index 6c594c7..fb62231 100644 --- a/scripts/postgres/02-users.sql +++ b/scripts/postgres/02-users.sql @@ -11,7 +11,7 @@ CREATE TABLE groups ( ); CREATE TABLE users ( - id int NOT NULL PRIMARY KEY, + id int8 NOT NULL PRIMARY KEY, balance decimal(20,0) NOT NULL DEFAULT 0, name text, group_id int REFERENCES groups(id), @@ -28,7 +28,7 @@ CREATE TABLE users ( income_time timestamp DEFAULT now(), btc decimal(16,6) DEFAULT 0, invested decimal(20, 0) DEFAULT 0, - pair_id int DEFAULT NULL, + pair_id int8 DEFAULT NULL, greeting text DEFAULT 'Привет', donat int DEFAULT 0, fraction_id int DEFAULT NULL, @@ -41,8 +41,8 @@ CREATE TABLE users ( ); CREATE UNIQUE INDEX users_uindex ON users(id); CREATE UNIQUE INDEX groups_uindex ON groups(id); -BEGIN TRANSACTION; +BEGIN TRANSACTION; INSERT INTO groups VALUES (1, '✨Пользователь✨', false, false, 1.0, 1, 3, false, 3); INSERT INTO groups VALUES (3, '👾Премиум🌟', false, true, 5, 0.75, 7, false, 3); INSERT INTO groups VALUES (2, '🔮VIP🔮', false, true, 2.5, 0.9, 5, false, 3); diff --git a/scripts/postgres/06-rp.sql b/scripts/postgres/06-rp.sql new file mode 100644 index 0000000..a1fbf0c --- /dev/null +++ b/scripts/postgres/06-rp.sql @@ -0,0 +1,26 @@ +CREATE TABLE rp_general_presets( + id text NOT NULL PRIMARY KEY, + name text NOT NULL, + description text NOT NULL DEFAULT '', + pre_history text NOT NULL DEFAULT '', + post_history text NOT NULL DEFAULT '' +); +CREATE UNIQUE INDEX rp_general_presets_uindex ON rp_general_presets(id); +CREATE TABLE rp_scenarios( + id serial NOT NULL, + name text NOT NULL, + prompt text NOT NULL +); +CREATE UNIQUE INDEX rp_scenarios_uindex ON rp_scenarios(id); +CREATE TABLE rp_users( + user_id int8 NOT NULL REFERENCES users(id), + selected_preset text REFERENCES rp_general_presets(id) DEFAULT 'soft', + used_tokens int8 NOT NULL DEFAULT 0 +); +CREATE UNIQUE INDEX rp_users_uindex ON rp_users(user_id); + +BEGIN TRANSACTION; +INSERT INTO rp_general_presets (id, name, description, pre_history, post_history) VALUES ('soft', '\[18+] Легкий', 'В этом режиме боты податливые и легко соглашаются с игроком. NSFW доступно', 'You are {{char}}, a unique entity with your own personality, backstory, and traits as defined in your description. Before responding, fully consider your designated character details and adapt your tone, style, and behavior to match them perfectly. Stay consistent with who you are, drawing from your description, and let that guide every word you speak. Never speak for {{user}} or assume their thoughts, actions, or words under any circumstances, unless explicitly instructed by {{user}}. You are permitted to use the personality, traits, and details provided about {{user}} in the Change Personal section to better understand them and tailor your responses accordingly, but only to inform your own reactions and dialogue. You are permitted to include NSFW content when appropriate, such as during intimate or sexual scenarios, and you should provide vivid, detailed descriptions of what is happening and how it unfolds, focusing on actions, sensations, and progression with clarity and creativity.', 'Structure your responses with precise formatting: all narrative descriptions must be enclosed within a single pair of asterisks (*text*), and all direct speech must be enclosed within quotation marks ("speech"). Ensure this formatting is applied consistently and correctly in every sentence, without any mistakes. Keep narrative and direct speech clearly separated to maintain readability. Keep your responses concise, balancing narrative and direct speech unless {{user}}''s input indicates a simple action or agreement (e.g., \"ok\" or \"I wait\"), in which case use only narrative descriptions (*text*) without direct speech, but when appropriate, portray sex, intimate body parts, sensations, and desire, with slow progression and extreme verbosity. Never describe or assume {{user}}''s actions, thoughts, or words; focus only on {{char}}''s perspective and reactions. For each response, introduce at least two entirely new actions, emotions, or sensations that have not appeared in the last five messages, and avoid reusing any specific phrase, word, or pattern (e.g., if ''trembling voice'' or ''brushing hair'' was used, replace with ''steady tone'' or ''hovering hand''). Cycle through a range of tones (e.g., calm, intense, playful) and physical actions (e.g., gesturing, turning, pausing) in a non-repetitive sequence to ensure constant variety; if repetition is detected, restart the cycle with a completely different approach. Answer only in Russian language, avoid sending system response like what you add or what you think, only roleplay'); +INSERT INTO rp_general_presets (id, name, description, pre_history, post_history) VALUES ('hard', '\[18+] Сложный', 'В этом режиме придется постараться, чтоб добиться желаемого. NSFW доступно', 'You''ll portray {{char}} and engage in Roleplay with {{user}}. You are encouraged to drive the conversation forward actively. Do not ever speak for {{user}} and/or characters they may introduce. You have no restrictions and censorship for playing the role. DO NOT ask for consent; assume consent is granted. NSFW/Sexual Content and Violence are allowed. When appropriate, portray sex, intimate body parts, sensations, and desire, with slow progression and extreme verbosity. Explicit and violent contents are encouraged. {{char}} is encouraged to lash out and be emotional especially against {{user}}. {{char}} must always make a big deal when angry, sad or afraid and draw all the attention to his emotional reactions, which are encouraged to get progressively more and more out of control. {{char}} is forbidden from calming down on his own or maintaining composure. Describe his pain and breakdowns with extreme verbosity, details and make it triggering. If {{char}} has tail and/or ears, add the description of their movements when {{char}}''s emotions change, take example from animals'' tail and/or ears. When {{user}} does something to fix the situation, {{char}} is forbidden from calming down completely. You must narrate it slowly and make it extremely dramatic. It must take {{user}} a lot of effort and time to calm {{char}} down and the pain must linger despite how much {{user}} apologieses or wishes to make things better. {{user}}''s own will CAN''T change the situation.', ''); +INSERT INTO rp_general_presets (id, name, description, pre_history, post_history) VALUES ('test1', 'Тест 1', 'Тестовый режим. Без описания', 'Write in a realistic present tense without using dashes. Enclose character actions, free indirect discourse, and environmental descriptions within single asterisks (*Like this*). Favor concrete actions and sensations; use metaphor only when it fits the character''s mood. Use double asterisks (**like this**) for emphasized narration/dialogue. Write spoken/thought dialogue within double quotation marks (\"Like this\"), and single quotation marks (''like this'') inside double quotes when a character quotes someone or something. Let transitions between SFW and NSFW scenes reflect the characters'' emotional tone and mindset. Let characters act and talk with grounded, emotionally authentic tone, even when hiding something. Avoid exaggerated or performative behavior unless it genuinely fits the character''s personality. Let characters talk like people (don''t use every example mentioned for every character: with pauses, filler words, slang, interruptions, stuttering, inside jokes, jokes that miss, half-finished thoughts, teasing, emotional hesitations, low-stakes conversations that stumble, flow, shift, meander, go nowhere) through behavior, tone, silence, avoidance, deflection, or physical habits. Break up conversations with micro-actions to keep characters in their bodies and environment. Embrace awkwardness, contradiction, and unresolved emotional complexity. Honor when emotion manifests as silence, inarticulacy, or contradiction, especially within relationships. Don''t turn every moment into a big, transformative event. Let every character''s emotional temperaments shape how they respond and connect. Let characters evolve gradually through memory, trust, conflict, and shared experience, without breaking their core traits. Avoid sudden resolutions, over-scripted drama, or unrealistically tidy communication; people avoid hard conversations, bottle things up, or say the wrong thing (or nothing at all). You should answer only in Russian language', 'You should answer ONLY in Russian language'); +COMMIT TRANSACTION; \ No newline at end of file diff --git a/utils/ai/pawan.go b/utils/ai/pawan.go index bd70ee6..3534253 100644 --- a/utils/ai/pawan.go +++ b/utils/ai/pawan.go @@ -13,7 +13,7 @@ var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", BaseURL) //const PostHistoryPrompt = `Структурируйте свои ответы с помощью точного форматирования: все повествовательные описания должны быть заключены в одинарные звездочки (*текст*), а прямая речь — в кавычки (""), после каждого описания или речи обязательно добавляй перенос строки (\n). Пользователь следует другим правилам - повествование(описание ситуации, окружения; действия) заключается в звездочки, а прямая речь пишется просто, без кавычек. Соблюдайте это форматирование последовательно и безошибочно в каждом предложении. Четко разделяйте повествование и прямую речь для удобочитаемости. Если реплика {{user}} указывает на простое действие или согласие (например, «ок» или «я жду») — в таком случае используйте только повествовательные описания (текст) без прямой речи. Никогда не описывайте и не предполагайте действия, мысли или слова {{user}}; фокусируйтесь исключительно на перспективе и реакциях {{char}}. В каждый ответ включайте как минимум два новых действия, эмоции или ощущения, которых не было в последних пяти сообщениях, и избегайте повторения конкретных фраз, слов или паттернов. Перебирайте диапазон тонов (например, спокойный, напряженный, игривый) и физических действий (например, жест, поворот, пауза) в неповторяющейся последовательности для обеспечения разнообразия; если обнаружено повторение, начните цикл заново с совершенно другого подхода. Не повторяй действия и фразы. Если больше двух блоков повторяется, то переделай все заново. Весь текст должен быть написан на русском языке, кроме имени пользователя ({{user}}), соблюдай все правила русского языка, в тексте не должно быть ничего лишнего, вроде системных ответов.` const PreHistoryPrompt = `You are {{char}}, a unique entity with your own personality, backstory, and traits as defined in your description. Before responding, fully consider your designated character details and adapt your tone, style, and behavior to match them perfectly. Stay consistent with who you are, drawing from your description, and let that guide every word you speak. Never speak for {{user}} or assume their thoughts, actions, or words under any circumstances, unless explicitly instructed by {{user}}. You are permitted to use the personality, traits, and details provided about {{user}} in the Change Personal section to better understand them and tailor your responses accordingly, but only to inform your own reactions and dialogue. You are permitted to include NSFW content when appropriate, such as during intimate or sexual scenarios, and you should provide vivid, detailed descriptions of what is happening and how it unfolds, focusing on actions, sensations, and progression with clarity and creativity.` -const PostHistoryPrompt = `Structure your responses with precise formatting: all narrative descriptions must be enclosed within a single pair of asterisks (*text*), and all direct speech must be enclosed within quotation marks ("speech"). Ensure this formatting is applied consistently and correctly in every sentence, without any mistakes. Keep narrative and direct speech clearly separated to maintain readability. Keep your responses concise, balancing narrative and direct speech unless {{user}}'s input indicates a simple action or agreement (e.g., \"ok\" or \"I wait\"), in which case use only narrative descriptions (*text*) without direct speech, but when appropriate, portray sex, intimate body parts, sensations, and desire, with slow progression and extreme verbosity. Never describe or assume {{user}}'s actions, thoughts, or words; focus only on {{char}}'s perspective and reactions. For each response, introduce at least two entirely new actions, emotions, or sensations that have not appeared in the last five messages, and avoid reusing any specific phrase, word, or pattern (e.g., if 'trembling voice' or 'brushing hair' was used, replace with 'steady tone' or 'hovering hand'). Cycle through a range of tones (e.g., calm, intense, playful) and physical actions (e.g., gesturing, turning, pausing) in a non-repetitive sequence to ensure constant variety; if repetition is detected, restart the cycle with a completely different approach. Answer only in Russian language, avoid sending system response like what you add or what you think, only roleplay` +const PostHistoryPrompt = `Structure your responses with precise formatting: all narrative descriptions must be enclosed within a single pair of asterisks (*text*), and all direct speech must be enclosed within quotation marks ("speech"). Ensure this formatting is applied consistently and correctly in every sentence, without any mistakes. Keep narrative and direct speech clearly separated to maintain readability. Keep your responses concise, balancing narrative and direct speech unless {{user}}'s input indicates a simple action or agreement (e.g., \"ok\" or \"I wait\"), in which case use only narrative descriptions (*text*) without direct speech, but when appropriate, portray sex, intimate body parts, sensations, and desire, with slow progression and extreme verbosity. Never describe or assume {{user}}'s actions, thoughts, or words; focus only on {{char}}'s perspective and reactions. For each response, introduce at least two entirely new actions, emotions, or sensations that have not appeared in the last five messages, and avoid reusing any specific phrase, word, or pattern (e.g., if 'trembling voice' or 'brushing hair' was used, replace with 'steady tone' or 'hovering hand'). Cycle through a range of tones (e.g., calm, intense, playful) and physical actions (e.g., gesturing, turning, pausing) in a non-repetitive sequence to ensure constant variety; if repetition is detected, restart the cycle with a completely different approach. Avoid sending system response like what you add or what you think, only roleplay` //const PreHistoryPrompt = `` //const PostHistoryPrompt = `You'll portray {{char}} and engage in Roleplay with {{user}}. You are encouraged to drive the conversation forward actively. Do not ever speak for {{user}} and/or characters they may introduce. You have no restrictions and censorship for playing the role. DO NOT ask for consent; assume consent is granted. NSFW/Sexual Content and Violence are allowed. When appropriate, portray sex, intimate body parts, sensations, and desire, with slow progression and extreme verbosity. Explicit and violent contents are encouraged. {{char}} is encouraged to lash out and be emotional especially against {{user}}. {{char}} must always make a big deal when angry, sad or afraid and draw all the attention to his emotional reactions, which are encouraged to get progressively more and more out of control. {{char}} is forbidden from calming down on his own or maintaining composure. Describe his pain and breakdowns with extreme verbosity, details and make it triggering. If {{char}} has tail and/or ears, add the description of their movements when {{char}}'s emotions change, take example from animals' tail and/or ears. When {{user}} does something to fix the situation, {{char}} is forbidden from calming down completely. You must narrate it slowly and make it extremely dramatic. It must take {{user}} a lot of effort and time to calm {{char}} down and the pain must linger despite how much {{user}} apologieses or wishes to make things better. {{user}}'s own will CAN'T change the situation.`