commit ae70c229f70a86edac80bf8238c8ccc916cb988e Author: ScuroNeko Date: Wed Aug 27 23:26:36 2025 +0300 initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..5549f9c --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +TG_TOKEN= +PREFIXES=/;! +DEBUG=true +USE_REQ_LOG=true +PSQL_USER= +PSQL_NAME= +PSQL_PASS= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e6d518 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +logs/ +.idea/ +config.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5652c15 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "laniakea"] + path = laniakea + url = https://git.nix13.pw/ScuroNeko/Laniakea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..59e8987 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM golang:1.25-alpine +WORKDIR /usr/src/kurumi +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -v -o /usr/local/bin/kurumi ./ +CMD ["kurumi"] \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..3fab3c2 --- /dev/null +++ b/build.bat @@ -0,0 +1,5 @@ +@echo off +set GOARCH=amd64 +set GOOS=linux +go build . +pause \ No newline at end of file diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..8ac4fa3 --- /dev/null +++ b/database/database.go @@ -0,0 +1,51 @@ +package database + +import ( + "encoding/json" + "fmt" + "io" + "log" + "os" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +type config struct { + Host string `json:"host"` + User string `json:"user"` + Password string `json:"password"` + Database string `json:"database"` +} + +var Database *gorm.DB +var conf *config + +func loadConfig() { + file, err := os.Open("./config.json") + if err != nil { + log.Fatalln(err) + } + data, err := io.ReadAll(file) + if err != nil { + log.Fatalln(err) + } + conf = new(config) + err = json.Unmarshal(data, conf) + if err != nil { + log.Fatalln(err) + } +} + +func getDSN() string { + return fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable", conf.User, conf.Password, conf.Host, conf.Database) +} + +func Connect() { + var err error + loadConfig() + Database, err = gorm.Open(postgres.Open(getDSN()), new(gorm.Config)) + if err != nil { + log.Fatalln(err) + } +} diff --git a/database/fraction.go b/database/fraction.go new file mode 100644 index 0000000..50a0b4c --- /dev/null +++ b/database/fraction.go @@ -0,0 +1,13 @@ +package database + +import "github.com/shopspring/decimal" + +type Fraction struct { + ID int + Name string + OwnerID int + Owner *User + Money decimal.Decimal + Exp int + Level int +} diff --git a/database/groups.go b/database/groups.go new file mode 100644 index 0000000..478bf0f --- /dev/null +++ b/database/groups.go @@ -0,0 +1,20 @@ +package database + +import "github.com/shopspring/decimal" + +type Group struct { + ID int + Name string + IsAdmin bool + IsVip bool + IsTester bool + Multiplier decimal.Decimal + Sale decimal.Decimal + MaxWaifus int +} + +// func GetGroupById(id int) (*Group, error) { +// group := new(Group) +// // err := Database.Get(group, "select * from groups where id=$1;", id) +// return group, err +// } diff --git a/database/shop.go b/database/shop.go new file mode 100644 index 0000000..5c63531 --- /dev/null +++ b/database/shop.go @@ -0,0 +1,30 @@ +package database + +import "github.com/shopspring/decimal" + +type ShopAuto struct { + ID int + Name string + Price decimal.Decimal +} + +type ShopBusiness struct { + ID int + Name string + Price decimal.Decimal + Income decimal.Decimal +} + +type ShopMaid struct { + ID int + Name string + Price decimal.Decimal + Income decimal.Decimal +} + +type ShopMiner struct { + ID int + Name string + Price decimal.Decimal + Income decimal.Decimal +} diff --git a/database/users.go b/database/users.go new file mode 100644 index 0000000..966f004 --- /dev/null +++ b/database/users.go @@ -0,0 +1,93 @@ +package database + +import ( + "database/sql" + "errors" + "math" + "time" + + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +type User struct { + ID int + TelegramID int `gorm:"column:tg_id"` + Balance decimal.Decimal + Name string + GroupID int + Group *Group + Level int + WorkID int + Work *Work + WorkTime time.Time + Exp int + AutoID int + Auto *ShopAuto + BusinessID int + Business *ShopBusiness + MaidID int + Maid *ShopMaid + MinerID int + Miner *ShopMiner + IncomeTime time.Time + BTC decimal.Decimal + Invested decimal.Decimal + PairID int + Pair *User + Greeting string + Donat int + FractionID sql.NullInt64 + Fraction *Fraction + + MoneyIncome decimal.Decimal + ExpIncome int + BtcIncome decimal.Decimal + + WaifuSearchTime time.Time +} + +func GetOrCreateUser(tgId int, name string) (*User, error) { + user, err := GetUser(tgId) + if errors.Is(err, gorm.ErrRecordNotFound) { + user, err = CreateUser(tgId, name) + } + return user, err +} + +func CreateUser(tgId int, name string) (*User, error) { + user := &User{ + TelegramID: tgId, + Name: name, + } + tx := Database.Create(user) + return user, tx.Error +} + +func GetUser(telegramId int) (*User, error) { + user := new(User) + tx := Database.Joins("Group").Joins("Work").Joins("Auto").Joins("Business").Joins("Maid").Joins("Miner").Joins("Fraction").Preload("Pair").Take(user, "tg_id=?", telegramId) + return user, tx.Error +} + +func GetAllUsers() ([]*User, error) { + users := make([]*User, 0) + tx := Database.Joins("Group").Joins("Work").Joins("Auto").Joins("Business").Joins("Maid").Joins("Miner").Joins("Fraction").Preload("Pair").Find(&users) + return users, tx.Error +} + +func CountLevel(userXp int) (int, int) { + xp := 0 + nextLevel := 2 + for { + xp = int(math.Pow(float64(nextLevel), 3)) * (nextLevel * 3) + if xp > userXp { + break + } + if nextLevel == 200 { + break + } + nextLevel++ + } + return nextLevel - 1, xp +} diff --git a/database/waifus.go b/database/waifus.go new file mode 100644 index 0000000..b18fa8d --- /dev/null +++ b/database/waifus.go @@ -0,0 +1,53 @@ +package database + +import "github.com/shopspring/decimal" + +type Waifu struct { + ID int + Name string + Rarity int + ExpBonus decimal.Decimal + MoneyBonus decimal.Decimal + MarketPrice decimal.Decimal + Fandom string + Image string + + OwnerID int + Owner *User +} + +func GetAllWaifus() ([]*Waifu, error) { + waifus := make([]*Waifu, 0) + tx := Database.Joins("Owner").Find(&waifus).Order("id") + return waifus, tx.Error +} + +func GetUserWaifus(userId int) ([]*Waifu, error) { + waifus := make([]*Waifu, 0) + tx := Database.Find(&waifus, "owner_id = ?", userId).Order("id") + return waifus, tx.Error +} + +func GetFreeWaifus() ([]*Waifu, error) { + waifus := make([]*Waifu, 0) + tx := Database.Find(&waifus, "owner_id is null").Order("id") + return waifus, tx.Error +} + +func GetFreeWaifusCount() (int64, error) { + var count int64 = 0 + tx := Database.Model(&Waifu{}).Where("owner_id is null").Count(&count) + return count, tx.Error +} + +func GetFreeWaifusWithRarity(rarity int) ([]*Waifu, error) { + waifus := make([]*Waifu, 0) + tx := Database.Find(&waifus, "owner_id is null and rarity = ?", rarity) + return waifus, tx.Error +} + +func GetWaifuById(id int) (*Waifu, error) { + waifu := new(Waifu) + tx := Database.Joins("Owner").Find(waifu, id) + return waifu, tx.Error +} diff --git a/database/works.go b/database/works.go new file mode 100644 index 0000000..6018b83 --- /dev/null +++ b/database/works.go @@ -0,0 +1,24 @@ +package database + +import "github.com/shopspring/decimal" + +type Work struct { + ID int + Name string + RequiredLevel int + MoneyIncome decimal.Decimal + MinExp int + MaxExp int +} + +func GetWorkById(id int) (*Work, error) { + work := new(Work) + tx := Database.First(work, id) + return work, tx.Error +} + +func GetAllWorks() ([]*Work, error) { + works := make([]*Work, 0) + tx := Database.Order("id").Find(&works) + return works, tx.Error +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f220b46 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module kurumibot + +go 1.25.0 + +require ( + github.com/fatih/color v1.18.0 + github.com/shopspring/decimal v1.4.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.30.1 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // 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/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3d2f486 --- /dev/null +++ b/go.sum @@ -0,0 +1,49 @@ +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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +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/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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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= +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/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +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.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= +gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/laniakea b/laniakea new file mode 160000 index 0000000..13eb3d4 --- /dev/null +++ b/laniakea @@ -0,0 +1 @@ +Subproject commit 13eb3d45de16c3d41d45b1e0726210e467c20d61 diff --git a/main.go b/main.go new file mode 100644 index 0000000..c02bcf8 --- /dev/null +++ b/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "kurumibot/database" + "kurumibot/plugins" + + "kurumibot/laniakea" +) + +func main() { + database.Connect() + bot := laniakea.NewBot(&laniakea.BotSettings{ + Token: "", + Debug: true, + Prefixes: laniakea.LoadPrefixesFromEnv(), + UseRequestLogger: true, + LoggerBasePath: "./logs/", + }) + bot = bot.ErrorTemplate("Во время выполнения команды произошла ошибка!\nСообщите об этом разработчику!\n\n%s") + plugins.RegisterEconomy(bot) + plugins.RegisterWaifus(bot) + plugins.RegisterAdmin(bot) + + defer bot.Close() + bot.Run() +} diff --git a/plugins/admin.go b/plugins/admin.go new file mode 100644 index 0000000..2832c15 --- /dev/null +++ b/plugins/admin.go @@ -0,0 +1,28 @@ +package plugins + +import ( + "kurumibot/database" + + "kurumibot/laniakea" +) + +func RegisterAdmin(b *laniakea.Bot) { + p := laniakea.NewPlugin("Admin") + p = p.Command(uploadPhoto, "uploadPhoto") + + b.AddPlugins(p.Build()) +} + +func uploadPhoto(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Msg.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + if !user.Group.IsAdmin { + return + } + + photoId := ctx.Msg.Photo[0].FileID + ctx.AnswerPhoto(photoId, photoId) +} diff --git a/plugins/economy.go b/plugins/economy.go new file mode 100644 index 0000000..9fee20a --- /dev/null +++ b/plugins/economy.go @@ -0,0 +1,356 @@ +package plugins + +import ( + "fmt" + "kurumibot/database" + "kurumibot/utils" + "math" + "math/rand/v2" + "strconv" + "strings" + "time" + + "kurumibot/laniakea" + + "github.com/shopspring/decimal" +) + +func RegisterEconomy(bot *laniakea.Bot) { + economy := laniakea.NewPlugin("Economy") + economy = economy.Command(profile, "profile", "профиль") + economy = economy.Command(work, "work", "работать") + economy = economy.Command(collect, "collect", "собрать") + economy = economy.Command(code, "code", "код") + + economy = economy.Command(vacancies, "vacancies", "вакансии") + economy = economy.Command(getAJob, "getajob", "устроиться") + + economy = economy.Command(aboutGroup, "group", "о группе") + + economy = economy.Command(about, "about", "о боте") + + go passiveIncome(bot) + + bot.AddPlugins(economy.Build()) +} + +func about(ctx *laniakea.MsgContext) { + out := []string{ + "Версия Go: 1.23.4", + fmt.Sprintf("Версия laniakea: %s", laniakea.VERSION_STRING), + } + ctx.Answer(strings.Join(out, "\n")) +} + +func passiveIncome(b *laniakea.Bot) { + for { + users, err := database.GetAllUsers() + if err != nil { + b.Logger().Error(err) + continue + } + + for _, user := range users { + if user.Business == nil && user.Maid == nil && user.Miner == nil { + continue + } + + if time.Now().Before(user.IncomeTime.Add(time.Hour * 3)) { + continue + } + + moneyIncome := decimal.NewFromInt(0) + expIncome := decimal.NewFromInt(0) + btcIncome := decimal.NewFromInt(0) + + if user.Business != nil { + moneyIncome = moneyIncome.Add(user.Business.Income).Mul(user.Group.Multiplier) + } + if user.Maid != nil { + expIncome = expIncome.Add(user.Maid.Income).Mul(user.Group.Multiplier) + } + if user.Miner != nil { + btcIncome = btcIncome.Add(user.Miner.Income).Mul(user.Group.Multiplier) + } + + waifus, err := database.GetUserWaifus(user.ID) + if err != nil { + b.Logger().Error(err) + continue + } + for _, waifu := range waifus { + moneyIncome = moneyIncome.Mul(waifu.MoneyBonus) + expIncome = expIncome.Mul(waifu.ExpBonus) + btcIncome = btcIncome.Mul(waifu.MoneyBonus) + } + + expIncome = expIncome.Mul(decimal.NewFromFloat(0.25)) + moneyIncome = moneyIncome.Mul(decimal.NewFromFloat(0.25)) + btcIncome = btcIncome.Mul(decimal.NewFromFloat(0.25)) + + user.ExpIncome += int(expIncome.IntPart()) + user.MoneyIncome = user.MoneyIncome.Add(moneyIncome) + user.BtcIncome = user.BtcIncome.Add(btcIncome) + user.IncomeTime = time.Now().Add(-time.Hour * 2) + database.Database.Save(user) + + b.Logger().Debug(fmt.Sprintf("У %d было пассивно собрано. След. сбор через час\n", user.TelegramID)) + } + + time.Sleep(time.Second * 5) + } +} + +func profile(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Update.Message.From.FirstName) + if err != nil { + ctx.Answer(err.Error()) + return + } + + pair := "нет" + if user.Pair != nil { + pair = user.Pair.Name + } + + fraction := "нет" + if user.Fraction != nil { + fraction = user.Fraction.Name + } + _, needXp := database.CountLevel(user.Exp) + + out := []string{ + fmt.Sprintf("🖤%s, %s🖤", user.Greeting, user.Name), + fmt.Sprintf("🆔ID: %d", user.ID), + fmt.Sprintf("🔰Фракция: %s", fraction), + fmt.Sprintf("📊Группа: %s (ID: %d, x%.1f)", user.Group.Name, user.Group.ID, user.Group.Multiplier.InexactFloat64()), + fmt.Sprintf("❤️Пара: %s", pair), + fmt.Sprintf("💡Уровень: %d (%d опыта, %d опыта до повышения)", user.Level, user.Exp, needXp-user.Exp), + fmt.Sprintf("💴Баланс: %s¥ %s, %s₿", utils.DecimalComma(&user.Balance), utils.Short(user.Balance.String()), utils.DecimalComma(&user.BTC)), + fmt.Sprintf("💼Работа: %s (ID: %d)", user.Work.Name, user.Work.ID), + fmt.Sprintf("🚘Авто: %s (ID: %d)", user.Auto.Name, user.Auto.ID), + fmt.Sprintf("🏢Бизнес: %s (ID: %d)", user.Business.Name, user.Business.ID), + fmt.Sprintf("👩‍🦳Горничная: %s (ID: %d)", user.Maid.Name, user.Maid.ID), + fmt.Sprintf("🖥Майнер: %s (ID: %d)", user.Miner.Name, user.Miner.ID), + } + ctx.Answer(strings.Join(out, "\n")) +} + +func work(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Update.Message.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + + if time.Now().Before(user.WorkTime.Add(time.Minute * 10)) { + nextTime := time.Since(user.WorkTime.Add(time.Minute * 10)) + seconds := int(math.Round(math.Abs(nextTime.Seconds()))) + minutes := seconds / 60 + seconds -= minutes * 60 + ctx.Answer(fmt.Sprintf("Приходи через %02d:%02d", minutes, seconds)) + return + } + work := user.Work + exp := rand.IntN(work.MaxExp-work.MinExp) + work.MinExp + // Count exp + expToAdd := decimal.NewFromInt(int64(exp)).Mul(user.Group.Multiplier) + // Count money + moneyToAdd := work.MoneyIncome.Mul(user.Group.Multiplier) + + waifus, err := database.GetUserWaifus(user.ID) + if err != nil { + ctx.Error(err) + return + } + for _, waifu := range waifus { + expToAdd = expToAdd.Mul(waifu.ExpBonus) + moneyToAdd = moneyToAdd.Mul(waifu.MoneyBonus) + } + user.Exp += int(expToAdd.IntPart()) + user.Balance = user.Balance.Add(moneyToAdd) + user.WorkTime = time.Now() + user.Level, _ = database.CountLevel(user.Exp) + database.Database.Save(user) + ctx.Answer(fmt.Sprintf("Ты заработал %s¥ и %d опыта.\nПриходи через 10 минут.", utils.DecimalComma(&work.MoneyIncome), expToAdd.IntPart())) +} + +func collect(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Update.Message.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + + if user.Business == nil && user.Maid == nil && user.Miner == nil { + ctx.Answer("Нечего собирать!") + return + } + + if time.Now().Before(user.IncomeTime.Add(time.Hour * 2)) { + nextTime := time.Since(user.IncomeTime.Add(time.Hour * 2)) + seconds := int(math.Round(math.Abs(nextTime.Seconds()))) + minutes := seconds / 60 + seconds -= minutes * 60 + hours := minutes / 60 + minutes -= hours * 60 + ctx.Answer(fmt.Sprintf("Приходи через %02d:%02d:%02d", hours, minutes, seconds)) + return + } + + moneyIncome := decimal.NewFromInt(0) + expIncome := decimal.NewFromInt(0) + btcIncome := decimal.NewFromInt(0) + + if user.Business != nil { + moneyIncome = moneyIncome.Add(user.Business.Income).Mul(user.Group.Multiplier) + } + if user.Maid != nil { + expIncome = expIncome.Add(user.Maid.Income).Mul(user.Group.Multiplier) + } + if user.Miner != nil { + btcIncome = btcIncome.Add(user.Miner.Income).Mul(user.Group.Multiplier) + } + + waifus, err := database.GetUserWaifus(user.ID) + if err != nil { + ctx.Error(err) + return + } + + for _, waifu := range waifus { + moneyIncome = moneyIncome.Mul(waifu.MoneyBonus) + expIncome = expIncome.Mul(waifu.ExpBonus) + btcIncome = btcIncome.Mul(waifu.MoneyBonus) + } + + incomeText := []string{} + + if moneyIncome.IntPart() > 0 { + incomeText = append(incomeText, fmt.Sprintf("%s¥", utils.DecimalComma(&moneyIncome))) + } + if expIncome.IntPart() > 0 { + incomeText = append(incomeText, fmt.Sprintf("%d опыта", expIncome.IntPart())) + } + if btcIncome.InexactFloat64() > 0 { + incomeText = append(incomeText, fmt.Sprintf("%.6f₿", btcIncome.InexactFloat64())) + } + + passiveIncomeText := []string{} + if user.MoneyIncome.IntPart() > 0 { + user.Balance = user.Balance.Add(user.MoneyIncome) + passiveIncomeText = append(passiveIncomeText, fmt.Sprintf("%s¥", utils.DecimalComma(&user.MoneyIncome))) + user.MoneyIncome = decimal.NewFromInt(0) + } + if user.ExpIncome > 0 { + user.Exp += user.ExpIncome + passiveIncomeText = append(passiveIncomeText, fmt.Sprintf("%d опыта", user.ExpIncome)) + user.ExpIncome = 0 + } + if user.BtcIncome.InexactFloat64() > 0 { + user.BTC = user.BTC.Add(user.BtcIncome) + passiveIncomeText = append(passiveIncomeText, fmt.Sprintf("%.6f₿", user.BtcIncome.InexactFloat64())) + user.BtcIncome = decimal.NewFromFloat(0.0000000) + } + + user.Balance = user.Balance.Add(moneyIncome) + user.Exp += int(expIncome.IntPart()) + user.Level, _ = database.CountLevel(user.Exp) + user.BTC = user.BTC.Add(btcIncome) + user.IncomeTime = time.Now() + + database.Database.Save(user) + + out := []string{ + fmt.Sprintf("Ты собрал %s.", strings.Join(incomeText, ", ")), + } + + if len(passiveIncomeText) > 0 { + out = append(out, fmt.Sprintf("Пока тебя не было, было собрано %s.", strings.Join(passiveIncomeText, ", "))) + } + + out = append(out, "Приходи через 02:00:00") + + ctx.Answer(strings.Join(out, "\n")) +} + +func code(ctx *laniakea.MsgContext) { + // user, err := database.Get +} + +func vacancies(ctx *laniakea.MsgContext) { + works, err := database.GetAllWorks() + + if err != nil { + ctx.Error(err) + return + } + + out := []string{ + "Список вакансий:", + } + + for _, work := range works { + out = append(out, fmt.Sprintf("%d) %s", work.ID, work.Name)) + } + + ctx.Answer(strings.Join(out, "\n")) +} + +func getAJob(ctx *laniakea.MsgContext) { + if len(ctx.Args) == 0 { + ctx.Answer("Недостаточно аргументов") + return + } + + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Msg.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + workId, err := strconv.Atoi(ctx.Args[0]) + if err != nil { + ctx.Error(err) + return + } + work, err := database.GetWorkById(workId) + if err != nil { + ctx.Error(err) + return + } + if user.Level < work.RequiredLevel { + ctx.Answer("Уровень слишком низкий для этой работы!") + return + } + user.WorkID = workId + user.Work = work + database.Database.Save(user) + ctx.Answer("Ты успешно устроился на работу!") +} + +func aboutGroup(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Msg.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + group := user.Group + out := []string{ + fmt.Sprintf("%s, %s!", user.Greeting, user.Name), + fmt.Sprintf("📝Твоя группа: %s", group.Name), + fmt.Sprintf("📉Твоя скидка: %.0f%%", 100-group.Sale.InexactFloat64()*100), + fmt.Sprintf("📈Твой множитель: x%.2f", group.Multiplier.InexactFloat64()), + fmt.Sprintf("👩🏼Максимум вайфу: %d", group.MaxWaifus), + } + if group.IsVip { + out = append(out, "🎏Доступ к ВИП функциям: ✅") + } + if group.IsAdmin { + out = append(out, "🖥️Доступ к админ функциям: ✅") + } + if group.IsVip { + out = append(out, "🔓Бета-тестер: ✅") + } + + ctx.Answer(strings.Join(out, "\n")) +} diff --git a/plugins/relations.go b/plugins/relations.go new file mode 100644 index 0000000..d3a515d --- /dev/null +++ b/plugins/relations.go @@ -0,0 +1,7 @@ +package plugins + +import "kurumibot/laniakea" + +func RegisterRelations(b *laniakea.Bot) { + +} diff --git a/plugins/waifus.go b/plugins/waifus.go new file mode 100644 index 0000000..0369304 --- /dev/null +++ b/plugins/waifus.go @@ -0,0 +1,175 @@ +package plugins + +import ( + "fmt" + "kurumibot/database" + "kurumibot/utils" + "strconv" + "strings" + "time" + + "kurumibot/laniakea" +) + +func RegisterWaifus(bot *laniakea.Bot) { + waifus := laniakea.NewPlugin("Waifus") + + waifus.Command(myWaifu, "mywaifu", "моивайфу") + waifus.Command(sellWaifu, "sellwaifu") + waifus.Command(waifuList, "wlist", "waifulist", "влист", "вайфулист") + waifus.Command(waifuInfo, "winfo", "waifuinfo", "винфо") + waifus.Command(waifuSearch, "wsearch", "waifusearch", "впоиск") + + bot.AddPlugins(waifus.Build()) +} + +func myWaifu(ctx *laniakea.MsgContext) { + user, err := database.GetUser(ctx.FromID) + if err != nil { + ctx.Answer(err.Error()) + return + } + + out := []string{ + fmt.Sprintf("%s, %s!", user.Greeting, user.Name), + "Список твоих вайфу:", + } + + waifus, err := database.GetUserWaifus(user.ID) + if err != nil { + ctx.Error(err) + return + } + for _, w := range waifus { + s := fmt.Sprintf("*%s* из \"*%s*\" (%d☆, ID: %d)", w.Name, w.Fandom, w.Rarity, w.ID) + out = append(out, s) + } + + ctx.Answer(strings.Join(out, "\n")) +} + +func sellWaifu(ctx *laniakea.MsgContext) { + +} + +func waifuList(ctx *laniakea.MsgContext) { + waifus, err := database.GetAllWaifus() + if err != nil { + ctx.Answer(err.Error()) + return + } + out := []string{} + + for _, w := range waifus { + var owner string + if w.Owner == nil { + owner = "нет" + } else { + owner = w.Owner.Name + } + s := fmt.Sprintf("*%s* из \"*%s*\" (%d☆, ID: %d) Владелец: %v", w.Name, w.Fandom, w.Rarity, w.ID, owner) + out = append(out, s) + } + + ctx.Answer(strings.Join(out, "\n")) +} + +func waifuInfo(ctx *laniakea.MsgContext) { + rawArgs := strings.TrimSpace(ctx.Text) + args := strings.Split(rawArgs, " ") + if len(args) != 1 { + ctx.Answer("Не указан ID вайфу!") + return + } + + waifuId, err := strconv.Atoi(args[0]) + if err != nil { + ctx.Answer("Во время выполнения команды произошла ошибка!") + return + } + + waifu, err := database.GetWaifuById(waifuId) + if err != nil { + ctx.Error(err) + return + } + + out := []string{ + fmt.Sprintf("*%s* из \"*%s*\"", waifu.Name, waifu.Fandom), + fmt.Sprintf("Редкость: %d☆", waifu.Rarity), + fmt.Sprintf("Бонус к деньгам: x%.2f", waifu.MoneyBonus.InexactFloat64()), + fmt.Sprintf("Бонус к опыту: x%.2f", waifu.ExpBonus.InexactFloat64()), + fmt.Sprintf("Рыночная стоимость: %s¥", utils.DecimalComma(&waifu.MarketPrice)), + } + if len(waifu.Image) > 0 { + ctx.AnswerPhoto(waifu.Image, strings.Join(out, "\n")) + } else { + ctx.Answer(strings.Join(out, "\n")) + } +} + +func waifuSearch(ctx *laniakea.MsgContext) { + user, err := database.GetOrCreateUser(ctx.FromID, ctx.Msg.From.FirstName) + if err != nil { + ctx.Error(err) + return + } + + if time.Now().Before(user.WaifuSearchTime.Add(time.Hour * 4)) { + ctx.Answer("Вайфу можно искать раз в 4 часа.") + return + } + + count, err := database.GetFreeWaifusCount() + if err != nil { + ctx.Error(err) + return + } + if count == 0 { + ctx.Answer("Не осталось свободных вайфу... :(\nПопробуй позже или купи на рынке!") + return + } + + rand := utils.RandRange(0, 100) + if 10 < rand && rand < 90 { + ctx.Answer("Ты ничего не нашел!") + return + } + + var freeWaifus []*database.Waifu + rarity := 3 + if rand == 0 { + rarity = 5 + } else if rand > 0 && rand < 7 { + rarity = 4 + } + + fmt.Println(rarity) + + freeWaifus, err = database.GetFreeWaifusWithRarity(rarity) + if err != nil { + ctx.Error(err) + return + } + + if len(freeWaifus) == 0 { + ctx.Answer("Ты ничего не нашел!") + return + } + + findedWaifuIndex := utils.RandRange(0, len(freeWaifus)) + waifu := freeWaifus[findedWaifuIndex] + out := []string{ + "Ты нашел новую вайфу!", + fmt.Sprintf("*%s* из \"*%s*\"", waifu.Name, waifu.Fandom), + fmt.Sprintf("Редкость: %d☆", waifu.Rarity), + fmt.Sprintf("Бонус к деньгам: x%.2f", waifu.MoneyBonus.InexactFloat64()), + fmt.Sprintf("Бонус к опыту: x%.2f", waifu.ExpBonus.InexactFloat64()), + fmt.Sprintf("Рыночная стоимость: %s¥", utils.DecimalComma(&waifu.MarketPrice)), + } + if len(waifu.Image) > 0 { + ctx.AnswerPhoto(waifu.Image, strings.Join(out, "\n")) + } else { + ctx.Answer(strings.Join(out, "\n")) + } +} diff --git a/utils/humanize.go b/utils/humanize.go new file mode 100644 index 0000000..8fb4d1b --- /dev/null +++ b/utils/humanize.go @@ -0,0 +1,104 @@ +package utils + +import ( + "fmt" + "log" + "math/big" + "strconv" + "strings" + + "github.com/shopspring/decimal" +) + +var NumberNames = map[int]string{ + 1: "тыс.", + 2: "млн.", + 3: "млрд.", + 4: "трлн.", + 5: "квадрлн.", + 6: "квинтлн.", + 7: "секстлн.", + 8: "септлн.", + 9: "октлн.", + 10: "нонилн.", + 11: "децилн.", + 12: "ундецилн.", + 13: "дуодецилн.", +} + +func Short(n string) string { + d, err := decimal.NewFromString(n) + if err != nil { + log.Fatalln(err) + } + comma := DecimalComma(&d) + sp := strings.Split(comma, "'") + if len(sp) == 0 { + return "" + } + return fmt.Sprintf("(%s %s)", sp[0], NumberNames[len(sp)-1]) +} + +// order of magnitude (to a max order) +func oomm(n, b *big.Int, maxmag int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + if mag == maxmag && maxmag >= 0 { + break + } + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} + +// total order of magnitude +// (same as above, but with no upper limit) +func oom(n, b *big.Int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} + +func BigComma(b *big.Int) string { + sign := "" + if b.Sign() < 0 { + sign = "-" + b.Abs(b) + } + + athousand := big.NewInt(1000) + c := (&big.Int{}).Set(b) + _, m := oom(c, athousand) + parts := make([]string, m+1) + j := len(parts) - 1 + + mod := &big.Int{} + for b.Cmp(athousand) >= 0 { + b.DivMod(b, athousand, mod) + parts[j] = strconv.FormatInt(mod.Int64(), 10) + switch len(parts[j]) { + case 2: + parts[j] = "0" + parts[j] + case 1: + parts[j] = "00" + parts[j] + } + j-- + } + parts[j] = strconv.Itoa(int(b.Int64())) + return sign + strings.Join(parts[j:], "'") +} + +func DecimalComma(d *decimal.Decimal) string { + if d.IsInteger() { + return BigComma(d.BigInt()) + } + fmt.Println(d.String()) + exp := strings.Split(d.String(), ".")[1] + return BigComma(d.BigInt()) + "." + exp +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..55d07fc --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,7 @@ +package utils + +import "math/rand/v2" + +func RandRange(min, max int) int { + return rand.IntN(max-min) + min +}