From dd83373689eaeea4456f934719e4f30f0c873603 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Tue, 17 Feb 2026 17:35:34 +0300 Subject: [PATCH] some fixes and changes --- .env | 3 +- database/red/rp_chats.go | 57 ++++++++++++++++++--- database/redis.go | 8 ++- go.mod | 1 + go.sum | 9 +++- plugins/admin.go | 33 ++++++++++-- plugins/economy.go | 13 ----- plugins/rp.go | 92 ++++++++++++++-------------------- plugins/service.go | 25 ++++++++- scripts/postgres/04-waifus.sql | 2 +- 10 files changed, 158 insertions(+), 85 deletions(-) diff --git a/.env b/.env index b555488..ec5b4a5 100644 --- a/.env +++ b/.env @@ -7,4 +7,5 @@ MONGO_HOST=127.0.0.1 MONGO_USER=kurumi_bot MONGO_PASS=kurumi_bot MONGO_NAME=kurumi -REDIS_HOST=127.0.0.1 \ No newline at end of file +REDIS_HOST=127.0.0.1 +REDIS_DB=0 \ No newline at end of file diff --git a/database/red/rp_chats.go b/database/red/rp_chats.go index 1525e83..017d07a 100644 --- a/database/red/rp_chats.go +++ b/database/red/rp_chats.go @@ -2,6 +2,8 @@ package red import ( "context" + "database/sql" + "errors" "fmt" "kurumibot/database/psql" "kurumibot/utils" @@ -71,8 +73,13 @@ func (rep RPRepository) GetOrCreateChatId(userId, waifuId int) (string, error) { err := rep.SetChatId(userId, waifuId, chatId) return chatId, err } -func (rep RPRepository) GetChat(userId, waifuId int) (RPChat, error) { +func (rep RPRepository) GetChat(userId int) (RPChat, error) { var chat RPChat + waifuId := rep.GetSelectedWaifu(userId) + if waifuId == 0 { + return chat, errors.New("no WaifuID") + } + chatId, err := rep.GetOrCreateChatId(userId, waifuId) if err != nil { return chat, err @@ -87,17 +94,28 @@ func (rep RPRepository) GetChat(userId, waifuId int) (RPChat, error) { chat.SettingID = rep.GetChatSettingID(userId, waifuId) psqlRep := psql.NewRPRepository(rep.db) - setting, err := psqlRep.GetSetting(chat.SettingID) - if err != nil { - return chat, err + if chat.SettingID > 0 { + setting, err := psqlRep.GetSetting(chat.SettingID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return chat, fmt.Errorf("setting %d not found", chat.SettingID) + } + return chat, err + } + chat.Setting = &setting } - chat.Setting = &setting chat.ScenariosIDs = rep.GetChatScenariosIDs(userId, waifuId) chat.Scenarios = make([]psql.RPScenario, len(chat.ScenariosIDs)) for i, id := range chat.ScenariosIDs { + if id <= 0 { + continue + } chat.Scenarios[i], err = psqlRep.GetScenario(id) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return chat, fmt.Errorf("scenario %d not found", id) + } return chat, err } } @@ -105,7 +123,6 @@ func (rep RPRepository) GetChat(userId, waifuId int) (RPChat, error) { } func (rep RPRepository) SaveChat(chat RPChat) error { - chatId := chat.ID.String() waifuId := chat.WaifuID userId := chat.UserID var err error @@ -119,6 +136,29 @@ func (rep RPRepository) SaveChat(chat RPChat) error { if err = rep.SetChatTokens(userId, waifuId, int(chat.ChatTokens)); err != nil { return err } + + var settingId int + if chat.Setting != nil { + settingId = chat.Setting.ID + } else { + settingId = chat.SettingID + } + if err = rep.SetChatSettingID(userId, waifuId, settingId); err != nil { + return err + } + + var ids []int + if len(chat.Scenarios) > 0 { + ids = utils.Map(chat.Scenarios, func(s psql.RPScenario) int { + return s.ID + }) + } else { + ids = chat.ScenariosIDs + } + if err = rep.SetChatScenariosIDs(userId, waifuId, ids); err != nil { + return err + } + return nil } func (rep RPRepository) SetChatPrompt(userId, waifuId int, prompt string) error { @@ -176,9 +216,10 @@ func (rep RPRepository) GetChatSettingID(userId, waifuId int) int { return i } -func (rep RPRepository) SetChatScenariosIDs(userId, waifuId int, scenarioIds string) error { +func (rep RPRepository) SetChatScenariosIDs(userId, waifuId int, scenarioIds []int) error { key := fmt.Sprintf("ai.chats.rp.%d.%d.scenario_id", userId, waifuId) - return rep.client.Set(ctx, key, scenarioIds, 0).Err() + ids := strings.Join(utils.Map(scenarioIds, utils.AnyToString), ",") + return rep.client.Set(ctx, key, ids, 0).Err() } func (rep RPRepository) GetChatScenariosIDs(userId, waifuId int) []int { key := fmt.Sprintf("ai.chats.rp.%d.%d.scenario_id", userId, waifuId) diff --git a/database/redis.go b/database/redis.go index 204e9be..d0fd962 100644 --- a/database/redis.go +++ b/database/redis.go @@ -3,6 +3,7 @@ package database import ( "fmt" "os" + "strconv" "github.com/redis/go-redis/v9" ) @@ -10,9 +11,14 @@ import ( var RedisClient *redis.Client func ConnectRedis() { + db, err := strconv.Atoi(os.Getenv("REDIS_DB")) + if err != nil { + panic(err) + } + RedisClient = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:6379", os.Getenv("REDIS_HOST")), Password: "", - DB: 0, + DB: db, }) } diff --git a/go.mod b/go.mod index b14c8a4..8fe713c 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/go.sum b/go.sum index 2a439b2..8484a9a 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ 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.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -42,8 +44,7 @@ github.com/muir/sqltoken v0.3.0 h1:3xbcqr80f3IA4OlwkOpdIHC4DTu6gsi1TwMqgYL4Dpg= github.com/muir/sqltoken v0.3.0/go.mod h1:+OSmbGI22QcVZ6DCzlHT8EAzEq/mqtqedtPP91Le+3A= 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.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= -github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= @@ -60,8 +61,12 @@ 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= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= diff --git a/plugins/admin.go b/plugins/admin.go index 28241a0..f662fbd 100644 --- a/plugins/admin.go +++ b/plugins/admin.go @@ -1,12 +1,15 @@ package plugins import ( + "encoding/json" "kurumibot/database/psql" "path/filepath" + "strings" "git.nix13.pw/scuroneko/laniakea" "git.nix13.pw/scuroneko/laniakea/tgapi" "git.nix13.pw/scuroneko/laniakea/utils" + "github.com/vinovest/sqlx" ) func RegisterAdmin(b *laniakea.Bot) { @@ -14,7 +17,7 @@ func RegisterAdmin(b *laniakea.Bot) { p.Command(uploadPhoto, "uploadPhoto") p.Command(emojiId, "emojiId") p.Command(getProxy, "proxy") - p.Command(test, "test") + p.Command(execSql, "sql") p.AddMiddleware(AdminMiddleware()) b.AddPlugins(p.Build()) @@ -31,8 +34,32 @@ func AdminMiddleware() *laniakea.PluginMiddleware { return m } -func test(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { - ctx.Answer("Ok") +func execSql(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { + stmt := strings.Join(ctx.Args, " ") + stmt = db.PostgresSQL.Rebind(stmt) + + var res []map[string]any + r, err := db.PostgresSQL.Query(stmt) + if err != nil { + ctx.Error(err) + return + } + defer r.Close() + for r.Next() { + a := make(map[string]any) + if err = sqlx.MapScan(r, a); err != nil { + ctx.Error(err) + return + } + res = append(res, a) + } + + data, err := json.MarshalIndent(res, "", " ") + if err != nil { + ctx.Error(err) + return + } + ctx.Answerf("`%s`", data) } func getProxy(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { ruProxy := "tg://proxy?port=3128&secret=7qaZyfQN-IQ7ZMwrR_zWnHBvem9uLnJ1&server=185.231.245.25" diff --git a/plugins/economy.go b/plugins/economy.go index 5baa91d..d818f9c 100644 --- a/plugins/economy.go +++ b/plugins/economy.go @@ -6,7 +6,6 @@ import ( "kurumibot/utils" "math" "math/rand/v2" - "runtime" "strconv" "strings" "time" @@ -27,8 +26,6 @@ func RegisterEconomy(bot *laniakea.Bot) { economy.Command(aboutGroup, "group", "о группе") - economy.Command(about, "about", "о боте") - bot.AddRunner(laniakea.NewRunner( "economy.PassiveIncome", passiveIncome, ).Timeout(time.Minute).Build()) @@ -36,16 +33,6 @@ func RegisterEconomy(bot *laniakea.Bot) { bot.AddPlugins(economy.Build()) } -func about(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { - out := []string{ - fmt.Sprintf("Версия Go: %s", runtime.Version()[2:]), - fmt.Sprintf("Версия Laniakea: %s", laniakea.VersionString), - fmt.Sprintf("Время сборки: %s", utils.BuildTime), - fmt.Sprintf("Git хеш: %s", utils.GitCommit), - } - ctx.Answer(strings.Join(out, "\n")) -} - func passiveIncome(b *laniakea.Bot) error { ctx := b.GetDBContext() waifuRep := psql.NewWaifuRepository(ctx) diff --git a/plugins/rp.go b/plugins/rp.go index a4b5459..9c6b67d 100644 --- a/plugins/rp.go +++ b/plugins/rp.go @@ -9,6 +9,7 @@ import ( "kurumibot/database/red" "kurumibot/utils" "kurumibot/utils/ai" + "log" "strconv" "strings" @@ -322,53 +323,40 @@ func rpSettingList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { func chatStat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { redisRpRep := red.NewRPRepository(db) - waifuId := redisRpRep.GetSelectedWaifu(ctx.FromID) - if waifuId == 0 { - ctx.Answer("Не выбрана вайфу") + chat, err := redisRpRep.GetChat(ctx.FromID) + if err != nil { + ctx.Error(err) return } - chatId := redisRpRep.GetChatId(ctx.FromID, waifuId) - if chatId == "" { - ctx.Answer("Нет активного чата") - return - } - messageCount, err := mdb.GetRPChatHistorySize(db, chatId) + messageCount, err := mdb.GetRPChatHistorySize(db, chat.ID.String()) if err != nil { ctx.Error(err) return } - tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId) kb := laniakea.NewInlineKeyboard(1) kb.AddCallbackButtonStyle("Сжать чат", laniakea.ButtonStyleSuccess, "rp.compress_chat") kb.AddCallbackButtonStyle("На главную", laniakea.ButtonStyleDanger, "rp.info") out := []string{ "Статистика чата", - fmt.Sprintf("*ID*: `%s`", chatId), + fmt.Sprintf("*ID*: `%s`", chat.ID.String()), fmt.Sprintf("*Кол-во сообщений*: %d", messageCount), - fmt.Sprintf("*Кол-во токенов*: %d", tokens), + fmt.Sprintf("*Кол-во токенов*: %d", chat.ChatTokens), } - psqlRpRep := psql.NewRPRepository(db) - scenarioIds := redisRpRep.GetChatScenariosIDs(ctx.FromID, waifuId) - if len(scenarioIds) > 0 && scenarioIds[0] > 0 { - scenarioNames := make([]string, len(scenarioIds)) - for i, id := range scenarioIds { - scenario, err := psqlRpRep.GetScenario(id) - if err != nil { - ctx.Error(err) + + if len(chat.Scenarios) > 0 && chat.Scenarios[0].ID > 0 { + scenarioNames := make([]string, len(chat.Scenarios)) + for i, scenario := range chat.Scenarios { + if scenario.ID == 0 { + continue } scenarioNames[i] = fmt.Sprintf("%s (ID: %d)", scenario.Name, scenario.ID) } out = append(out, fmt.Sprintf("*Выбранные сценарии*: %s", strings.Join(scenarioNames, ", "))) } - settingId := redisRpRep.GetChatSettingID(ctx.FromID, waifuId) - if settingId > 0 { - setting, err := psqlRpRep.GetSetting(settingId) - if err != nil { - ctx.Error(err) - } - out = append(out, fmt.Sprintf("*Выбранный сеттинг*: %s (ID: %d)", setting.Name, setting.ID)) + if chat.Setting != nil { + out = append(out, fmt.Sprintf("*Выбранный сеттинг*: %s (ID: %d)", chat.Setting.Name, chat.Setting.ID)) } ctx.EditCallback(strings.Join(out, "\n"), kb) ctx.AnswerCbQuery() @@ -449,7 +437,7 @@ func newChatStage2(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { if len(scenariosIds) == 0 { scenariosIds = append(scenariosIds, 0) } - kb.AddCallbackButton("Продолжить", "rp.new_chat", utils.AppendToInt(settingId, selectedScenariosIds)...) + kb.AddCallbackButtonStyle("Создать", laniakea.ButtonStyleSuccess, "rp.new_chat", utils.AppendToInt(settingId, selectedScenariosIds)...) kb.AddLine() kb.AddCallbackButtonStyle("Назад", laniakea.ButtonStyleDanger, "rp.new_chat_s1") ctx.EditCallback(strings.Join(out, "\n"), kb) @@ -476,6 +464,13 @@ func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { return } + chat, err := redisRpRep.GetChat(ctx.FromID) + if err != nil { + ctx.Error(err) + return + } + + log.Println(ctx.Args) chatPrompt := "" settingId, err := strconv.Atoi(ctx.Args[0]) if err != nil { @@ -483,7 +478,7 @@ func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { return } if settingId > 0 { - scenario, err := psqlRpRep.GetSetting(settingId) + setting, err := psqlRpRep.GetSetting(settingId) if err != nil { if errors.Is(err, sql.ErrNoRows) { ctx.Answerf("Сеттинг №%d не найден", settingId) @@ -492,10 +487,13 @@ func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } return } - chatPrompt = "Вот краткое описание мира(сеттинг): " + scenario.Prompt + "." + chat.Setting = &setting + chat.SettingID = settingId + chatPrompt = "Вот краткое описание мира(сеттинг): " + setting.Prompt + "." } scenariosIds := utils.Map(ctx.Args[1:], utils.StringToInt) + chat.Scenarios = make([]psql.RPScenario, 0) if len(scenariosIds) > 0 && scenariosIds[0] > 0 { rep := psql.NewRPRepository(db) var scenariosPrompt []string @@ -510,39 +508,23 @@ func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } return } + chat.Scenarios = append(chat.Scenarios, scenario) scenariosPrompt = append(scenariosPrompt, scenario.Prompt) } chatPrompt += "Вот дополнительная информация: " + strings.Join(scenariosPrompt, ".") } - - if chatPrompt != "" { - err = redisRpRep.SetChatPrompt(ctx.FromID, waifuId, chatPrompt) - if err != nil { - ctx.Error(err) - return - } - } - - err = redisRpRep.SetChatSettingID(ctx.FromID, waifuId, settingId) - if err != nil { - ctx.Error(err) - } - err = redisRpRep.SetChatScenariosIDs(ctx.FromID, waifuId, strings.Join(utils.Map(scenariosIds, utils.AnyToString), ",")) - if err != nil { - ctx.Error(err) - } - err = redisRpRep.SetCounter(ctx.FromID, waifuId, 0) - if err != nil { - ctx.Error(err) - } - err = redisRpRep.SetChatTokens(ctx.FromID, waifuId, 0) + chat.Prompt = chatPrompt + chat.Counter = 0 + chat.ChatTokens = 0 + err = redisRpRep.SaveChat(chat) if err != nil { ctx.Error(err) + return } kb := laniakea.NewInlineKeyboard(2) - kb.AddCallbackButton("На главную", "rp.info") + kb.AddCallbackButtonStyle("На главную", laniakea.ButtonStyleSuccess, "rp.info") kb.AddCallbackButtonStyle("Закрыть", laniakea.ButtonStyleDanger, "general.close") ctx.EditCallback("Был создан новый чат. Для общения используй `/г промпт`.", kb) ctx.AnswerCbQuery() @@ -666,7 +648,7 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { ctx.SendAction(tgapi.ChatActionTyping) api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", rpUser.Model.Key) defer api.Close() - res, err := api.CreateCompletion(messages, userMessage, 1.0) + res, err := api.CreateCompletion(messages, userMessage, 0.5) if err != nil { ctx.Error(err) return @@ -784,7 +766,7 @@ func regenerateResponse(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) messages = messages.Pop(count - 2) ctx.EditCallback("Генерация запущена...", nil) - res, err := api.CreateCompletion(messages, userReq.Message, 1.0) + res, err := api.CreateCompletion(messages, userReq.Message, 0.5) if err != nil { ctx.Error(err) return diff --git a/plugins/service.go b/plugins/service.go index 70302b5..8292a1b 100644 --- a/plugins/service.go +++ b/plugins/service.go @@ -1,13 +1,36 @@ package plugins -import "git.nix13.pw/scuroneko/laniakea" +import ( + "fmt" + "kurumibot/utils" + "runtime" + "strings" + + "git.nix13.pw/scuroneko/laniakea" +) func RegisterService(bot *laniakea.Bot) { p := laniakea.NewPlugin("service") p.Payload(generalClose, "general.close") + + p.Command(about, "about", "о боте") bot.AddPlugins(p.Build()) } +func about(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { + out := []string{ + fmt.Sprintf("Версия Go: %s", runtime.Version()[2:]), + fmt.Sprintf("Версия Laniakea: %s", laniakea.VersionString), + fmt.Sprintf("Время сборки: %s", utils.BuildTime), + fmt.Sprintf("Git хеш: %s", utils.GitCommit), + } + + kb := laniakea.NewInlineKeyboard(2) + kb.AddUrlButtonStyle("Канал", laniakea.ButtonStylePrimary, "https://t.me/ym_gbot_news") + kb.AddUrlButtonStyle("Чат", laniakea.ButtonStylePrimary, "https://t.me/ym_gbot_chat") + ctx.Keyboard(strings.Join(out, "\n"), kb) +} + func generalClose(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { ctx.CallbackDelete() ctx.AnswerCbQuery() diff --git a/scripts/postgres/04-waifus.sql b/scripts/postgres/04-waifus.sql index 97bfb7d..8046ae4 100644 --- a/scripts/postgres/04-waifus.sql +++ b/scripts/postgres/04-waifus.sql @@ -22,7 +22,7 @@ INSERT INTO waifus VALUES (4, null, 'Джейн Доу', 5, 2.0, 2.0, 1000000000 INSERT INTO waifus VALUES (5, null, 'Элизия', 5, 2.0, 2.0, 10000000000000, 'Honkai: Star rail', 'AgACAgIAAxkBAAIL1mlvLPQpjTnZ4Ok1KjERIFdByLryAAJlEGsbA6N5SxUEOOHNFSCdAQADAgADcwADOAQ', ''); INSERT INTO waifus VALUES (6, null, 'Светлячок', 5, 2.0, 2.0, 10000000000000, 'Honkai: Star rail', 'AgACAgIAAxkBAAIL12lvLReROZAH1gKY-IL57U5ElUBcAAJnEGsbA6N5S5HV_my-RbTqAQADAgADcwADOAQ', ''); INSERT INTO waifus VALUES (7, 314834933, 'Клоринда', 5, 2.0, 2.0, 10000000000000, 'Genshin Impact', 'AgACAgIAAxkBAAIL2GlvLRyWLv6LGH6pJ71xn-6KoCF2AAJoEGsbA6N5SzBJlZ4WM9sGAQADAgADcwADOAQ', 'Клоринда — высокая девушка с фиолетовыми глазами, носит строгое, вдохновлённое мушкетёрами, одеяние. Основные черты внешности: Рост 168 см, волосы собраны в длинный хвост спереди оставлены свободные пряди, глаза фиолетовые. Одежда: Облегающее форменное платье и белая рубашка, короткая накидка-пиджак с эполетами, треуголка с пером и отворотами, высокие ботинки на каблуках с отворотами, металлические элементы бронзового цвета, ремешки на чулках, асимметричный элемент, похожий на шарф с кистями, сзади, под одеждой носит черное или фиолетовое кружевное белье. На неё теле нет ни единого шрама. Волосы на лобке всегда гладко выбриты. Ключевые черты характера и поведения: Воин и Правосудие: Она — воплощение справедливости, её боятся злодеи и те, кто стремится к славе обманным путём. Непоколебимая: Несмотря на потерю близких, она не сдалась, а стала защитником Фонтейна, демонстрируя силу и решимость. Загадочность: Публично не даёт интервью, окутывая свой успех тайной, но её «ритуал» очищения меча — это на самом деле медитация для сохранения спокойствия и концентрации. Практичность: Сочетает ближний и дальний бой, используя пистолет и меч, что делает её эффективной против любых врагов.'); -INSERT INTO waifus VALUES (8, null, 'Райден Эи', 5, 2.0, 2.0, 10000000000000, 'Genshin Impact', 'AgACAgIAAxkBAAIL2WlvLSYSdHFkdTm1txud3qJ7mV4dAAJpEGsbA6N5S3hLErFkYwWuAQADAgADcwADOAQ', ''); +INSERT INTO waifus VALUES (8, null, 'Райден Эи', 5, 2.0, 2.0, 10000000000000, 'Genshin Impact', 'AgACAgIAAxkBAAIL2WlvLSYSdHFkdTm1txud3qJ7mV4dAAJpEGsbA6N5S3hLErFkYwWuAQADAgADcwADOAQ', 'Райдэн Эй (Сёгун Райдэн) — высокая(167 см), статная женщина с бледной кожей, яркими фиолетовыми глазами и маленькой родинкой под правым глазом. У нее длинные темно-фиолетовые волосы, уложенные в одну сложную косу, спадающую на спину. Ее наряд представляет собой детализированное темно-фиолетовое и лавандовое кимоно с символами томоэ, алую ленту-чокер и черные чулки, а также украшения для волос — веерообразную шпильку и цветы, — которые раньше принадлежали ее сестре-близнецу, Макото. Волосы и глаза: Длинные, заплетенные в косу темно-фиолетовые волосы, иногда описываемые как имеющие более светлые, светящиеся лавандовые кончики при использовании элементальных способностей. У нее поразительные, светло-фиолетовые/лиловые глаза. Украшение для волос: На правой стороне головы она носит шпильку в форме веера (оги-бира кандзаси), украшенную бледно-лиловыми цветами, похожими на колокольчики. Одежда: Короткое темно-фиолетовое и лавандовое кимоно с запа́хом. На левом плече — черное бронированное наплечье, а сзади — малиновый бант оби. Аксессуары: На ней надеты темно-фиолетовые чулки до бедра, сандалии на высоком каблуке и маленький красный чокер-лента. Носит черное кружевное белье. Райдэн Эй — суровый, но эмоционально сложный Электро Архонт Инадзумы, чья личность сформирована глубоким страхом потери и упрямым, навязчивым стремлением к «вечности», чтобы защитить свой народ от эрозии. В то время как марионетка-сёгун действует как безжалостный исполнитель, сама Эй более общительна, любит сладости и ведет себя как воин, изначально изолировавшая себя, чтобы справиться с горем прошлого. Ключевые черты характера: Стоична, но заботлива: Движимая травмой потери сестры-близнеца Макото, Эй стремится законсервировать Инадзуму в статичном состоянии, чтобы предотвратить дальнейшие страдания. Упряма и целеустремленна: Полностью сосредоточившись на своем видении «вечности», она поначалу была безразлична к нюансам повседневной жизни или страданиям, вызванным ее указами. Наивна в вопросах управления: Несмотря на то, что она грозный воин, Эй оторвана от повседневного управления Инадзумой, полагаясь на марионетку. Любознательна и развивается: После завершения своей сюжетной арки она проявляет готовность учиться, адаптироваться и признавать свои ошибки, переходя от статуса жесткого, изолированного правителя к более заботливому, хоть и все еще суровому, хранителю. Человечные черты: Как ни удивительно, она обожает сладости, дорожит воспоминаниями и не является жестокой по своей природе, проявляя милосердие к своим подданным. Характер Эй — это сложное сочетание могущественного воина, скорбящей сестры и заблуждающегося, заботливого лидера, который эволюционирует от эмоционально отстраненного до понимания, что истинная вечность заключается в сердцах и памяти ее народа.'); INSERT INTO waifus VALUES (9, null, 'Марин Китагава', 5, 2.0, 2.0, 10000000000000, 'Эта фарфоровая кукла влюбилась', '', ''); INSERT INTO waifus VALUES (10, 7915366224, 'Надзана Нанакуса', 5, 2.0, 2.0, 10000000000000, 'Песнь ночных сов', '', ''); INSERT INTO waifus VALUES (11, null, 'Эвелин Шевалье', 5, 2.0, 2.0, 10000000000000, 'Zenless Zone Zero', '', 'Эвелин Шевалье — женщина ростом 173 см со светлой кожей, выразительными фиолетовыми глазами и светлыми волосами, которые часто уложены в короткую волнистую каре или причёску с пучком. Будучи бывшей шпионкой и телохранителем, она имеет стройную, пропорциональную фигуру и носит стильный, элегантный ансамбль в чёрно-белых тонах, включающий белую блузу, чёрный жилет-хамес и плащ с красной подкладкой. Обычно на ней белая безрукавка с высоким воротником, чёрный галстук, чёрный жилет-хамес и облегающие латексные брюки чёрного цвета с глянцевым блеском, ремнями и пряжками. Под одеждой она носит простое белое или черное белье. Она известна своим холодным, профессиональным и серьёзным поведением, выступая в роли телохранителя и менеджера Астры Яо. Эвелин Шевалье — спокойный, высокопрофессиональный и исключительно эффективный менеджер и телохранитель Астры Яо. Её описывают как «решительную и отважную» личность с «упрямым» характером. Она обладает дотошным вниманием к деталям и сохраняет «стоическое» спокойствие, будучи при этом яростно защищающей своих подопечных. Несмотря на «загадочное» прошлое и «скрытую», «чуткую» и «заботливую» натуру, внешне она производит впечатление «холодного» и «серьёзного» человека, который «всегда» «готов», «опытен», «собран», «организован» и «надежен».');