initial commit

This commit is contained in:
2025-08-27 23:26:36 +03:00
commit ae70c229f7
22 changed files with 1087 additions and 0 deletions

7
.env Normal file
View File

@@ -0,0 +1,7 @@
TG_TOKEN=
PREFIXES=/;!
DEBUG=true
USE_REQ_LOG=true
PSQL_USER=
PSQL_NAME=
PSQL_PASS=

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
logs/
.idea/
config.json

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "laniakea"]
path = laniakea
url = https://git.nix13.pw/ScuroNeko/Laniakea

7
Dockerfile Normal file
View File

@@ -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"]

5
build.bat Normal file
View File

@@ -0,0 +1,5 @@
@echo off
set GOARCH=amd64
set GOOS=linux
go build .
pause

51
database/database.go Normal file
View File

@@ -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)
}
}

13
database/fraction.go Normal file
View File

@@ -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
}

20
database/groups.go Normal file
View File

@@ -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
// }

30
database/shop.go Normal file
View File

@@ -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
}

93
database/users.go Normal file
View File

@@ -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
}

53
database/waifus.go Normal file
View File

@@ -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
}

24
database/works.go Normal file
View File

@@ -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
}

25
go.mod Normal file
View File

@@ -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
)

49
go.sum Normal file
View File

@@ -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=

1
laniakea Submodule

Submodule laniakea added at 13eb3d45de

26
main.go Normal file
View File

@@ -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()
}

28
plugins/admin.go Normal file
View File

@@ -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)
}

356
plugins/economy.go Normal file
View File

@@ -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"))
}

7
plugins/relations.go Normal file
View File

@@ -0,0 +1,7 @@
package plugins
import "kurumibot/laniakea"
func RegisterRelations(b *laniakea.Bot) {
}

175
plugins/waifus.go Normal file
View File

@@ -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"))
}
}

104
utils/humanize.go Normal file
View File

@@ -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
}

7
utils/utils.go Normal file
View File

@@ -0,0 +1,7 @@
package utils
import "math/rand/v2"
func RandRange(min, max int) int {
return rand.IntN(max-min) + min
}