From b8226d8aa8de92446ffa902ecb43a1cb94bf3442 Mon Sep 17 00:00:00 2001 From: Vladimir Hodakov Date: Mon, 29 Jan 2018 23:50:25 +0400 Subject: [PATCH] DataCache and changes for game update Recent game update changed pokememes view in pokedeks, so we need to reflect it by updating parser. Introducing DataCache - a silver bullet for eliminating lags linked to database queries. Less queries, more in RAM, faster work. Needs testing in production environment. --- Gopkg.lock | 61 ++-- Gopkg.toml | 44 ++- cmd/i2_bot/i2_bot.go | 4 +- lib/appcontext/appcontext.go | 18 +- lib/chatter/restricters.go | 11 +- lib/config/config.go | 2 +- lib/connections/connections.go | 4 +- .../datacacheinterface/datacacheinterface.go | 34 ++ lib/datacache/elements.go | 45 +++ lib/datacache/exported.go | 76 +++++ lib/datacache/leagues.go | 48 +++ lib/datacache/locations.go | 45 +++ lib/datacache/players.go | 157 +++++++++ lib/datacache/pokememes.go | 297 +++++++++++++++++ lib/datacache/profiles.go | 118 +++++++ lib/datacache/weapons.go | 48 +++ lib/dbmapping/profiles.go | 21 -- lib/pokedexer/cleaners.go | 22 +- lib/pokedexer/getters.go | 184 ++--------- lib/pokedexer/parsers.go | 311 ++++++------------ lib/pokedexer/pokedexer.go | 18 +- .../pokedexerinterface/pokedexerinterface.go | 5 +- lib/pokedexer/responders.go | 30 +- lib/router/callback_request.go | 9 +- lib/router/inline.go | 7 +- lib/router/router.go | 12 +- lib/squader/getters.go | 6 +- lib/squader/restricters.go | 7 +- lib/squader/squader.go | 27 +- lib/statistics/squads.go | 14 +- lib/users/getters.go | 54 +-- lib/users/parsers.go | 63 ++-- lib/users/responders.go | 42 +-- lib/users/users.go | 136 ++++---- lib/users/usersinterface/usersinterface.go | 3 - lib/welcomer/welcomer.go | 11 +- 36 files changed, 1294 insertions(+), 700 deletions(-) create mode 100644 lib/datacache/datacacheinterface/datacacheinterface.go create mode 100644 lib/datacache/elements.go create mode 100644 lib/datacache/exported.go create mode 100644 lib/datacache/leagues.go create mode 100644 lib/datacache/locations.go create mode 100644 lib/datacache/players.go create mode 100644 lib/datacache/pokememes.go create mode 100644 lib/datacache/profiles.go create mode 100644 lib/datacache/weapons.go diff --git a/Gopkg.lock b/Gopkg.lock index 486d133..5ec0d02 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,18 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "bitbucket.org/pztrn/flagger" + packages = ["."] + revision = "c9b4726e1787adb01126cdf9a7932e2bccbb51a7" + +[[projects]] + branch = "master" + name = "bitbucket.org/pztrn/mogrus" + packages = ["."] + revision = "2e3a9d38c8e4b0036f714f88ef0fe9fc14b4c6b8" + [[projects]] name = "github.com/go-sql-driver/mysql" packages = ["."] @@ -16,8 +28,11 @@ [[projects]] branch = "master" name = "github.com/jmoiron/sqlx" - packages = [".","reflectx"] - revision = "3379e5993990b1f927fc8db926485e6f6becf2d2" + packages = [ + ".", + "reflectx" + ] + revision = "05cef0741ade10ca668982355b3f3f0bcf0ff0a8" [[projects]] name = "github.com/pressly/goose" @@ -34,8 +49,8 @@ [[projects]] name = "github.com/sirupsen/logrus" packages = ["."] - revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" - version = "v1.0.3" + revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba" + version = "v1.0.4" [[projects]] name = "github.com/technoweenie/multipartstreamer" @@ -47,35 +62,39 @@ branch = "master" name = "golang.org/x/crypto" packages = ["ssh/terminal"] - revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94" + revision = "0efb9460aaf800c6376acf625be2853bceac2e06" [[projects]] branch = "master" name = "golang.org/x/sys" - packages = ["unix","windows"] - revision = "665f6529cca930e27b831a0d1dafffbe1c172924" + packages = [ + "unix", + "windows" + ] + revision = "ff2a66f350cefa5c93a634eadb5d25bb60c85a9c" + +[[projects]] + branch = "master" + name = "golang.org/x/text" + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" [[projects]] branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" - -[[projects]] - branch = "master" - name = "lab.pztrn.name/golibs/flagger" - packages = ["."] - revision = "5cf6a6dd8fe8a4f37d1bb1e3deb5121c6e923668" - -[[projects]] - branch = "master" - name = "lab.pztrn.name/golibs/mogrus" - packages = ["."] - revision = "a888e29e1fff06c2e6ebc607055a02de22a6ddae" + revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e26a19cb9a3e96a96b1d51599ead99341edc2278667ddf8a7dac4cd197fbe09d" + inputs-digest = "99811036e66782056cb66f37e4a2570ac81f0e3da2fe399c4d54d83acc5a368d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 419f88d..3f7f980 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,4 +1,3 @@ - # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md @@ -17,16 +16,51 @@ # source = "github.com/myfork/project2" # # [[override]] -# name = "github.com/x/y" -# version = "2.4.0" +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true -required = ["github.com/go-sql-driver/mysql", "github.com/jmoiron/sqlx", "github.com/pressly/goose"] + +[[constraint]] + branch = "master" + name = "bitbucket.org/pztrn/flagger" + +[[constraint]] + branch = "master" + name = "bitbucket.org/pztrn/mogrus" + +[[constraint]] + name = "github.com/go-sql-driver/mysql" + version = "1.3.0" [[constraint]] name = "github.com/go-telegram-bot-api/telegram-bot-api" - version = "4.6.0" + version = "4.6.1" + +[[constraint]] + branch = "master" + name = "github.com/jmoiron/sqlx" + +[[constraint]] + name = "github.com/pressly/goose" + version = "2.1.0" + +[[constraint]] + name = "github.com/robfig/cron" + version = "1.0.0" + +[[constraint]] + branch = "master" + name = "golang.org/x/text" [[constraint]] branch = "v2" name = "gopkg.in/yaml.v2" +[prune] + go-tests = true + unused-packages = true diff --git a/cmd/i2_bot/i2_bot.go b/cmd/i2_bot/i2_bot.go index 62e8f22..21197f8 100644 --- a/cmd/i2_bot/i2_bot.go +++ b/cmd/i2_bot/i2_bot.go @@ -4,10 +4,10 @@ package main import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/appcontext" "git.wtfteam.pro/fat0troll/i2_bot/lib/broadcaster" "git.wtfteam.pro/fat0troll/i2_bot/lib/chatter" + "git.wtfteam.pro/fat0troll/i2_bot/lib/datacache" "git.wtfteam.pro/fat0troll/i2_bot/lib/forwarder" "git.wtfteam.pro/fat0troll/i2_bot/lib/migrations" "git.wtfteam.pro/fat0troll/i2_bot/lib/orders" @@ -20,6 +20,7 @@ import ( "git.wtfteam.pro/fat0troll/i2_bot/lib/talkers" "git.wtfteam.pro/fat0troll/i2_bot/lib/users" "git.wtfteam.pro/fat0troll/i2_bot/lib/welcomer" + "github.com/go-telegram-bot-api/telegram-bot-api" "time" ) @@ -34,6 +35,7 @@ func main() { router.New(c) migrations.New(c) c.RunDatabaseMigrations() + datacache.New(c) forwarder.New(c) pokedexer.New(c) pinner.New(c) diff --git a/lib/appcontext/appcontext.go b/lib/appcontext/appcontext.go index a5d4383..dc648fd 100644 --- a/lib/appcontext/appcontext.go +++ b/lib/appcontext/appcontext.go @@ -4,13 +4,13 @@ package appcontext import ( - "github.com/go-telegram-bot-api/telegram-bot-api" - "github.com/jmoiron/sqlx" - "github.com/robfig/cron" + "bitbucket.org/pztrn/flagger" + "bitbucket.org/pztrn/mogrus" "git.wtfteam.pro/fat0troll/i2_bot/lib/broadcaster/broadcasterinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/chatter/chatterinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/config" "git.wtfteam.pro/fat0troll/i2_bot/lib/connections" + "git.wtfteam.pro/fat0troll/i2_bot/lib/datacache/datacacheinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/forwarder/forwarderinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/migrations/migrationsinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/orders/ordersinterface" @@ -23,8 +23,9 @@ import ( "git.wtfteam.pro/fat0troll/i2_bot/lib/talkers/talkersinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/users/usersinterface" "git.wtfteam.pro/fat0troll/i2_bot/lib/welcomer/welcomerinterface" - "lab.pztrn.name/golibs/flagger" - "lab.pztrn.name/golibs/mogrus" + "github.com/go-telegram-bot-api/telegram-bot-api" + "github.com/jmoiron/sqlx" + "github.com/robfig/cron" "os" ) @@ -35,6 +36,7 @@ type Context struct { Cron *cron.Cron Log *mogrus.LoggerHandler Bot *tgbotapi.BotAPI + DataCache datacacheinterface.DataCacheInterface Forwarder forwarderinterface.ForwarderInterface Migrations migrationsinterface.MigrationsInterface Router routerinterface.RouterInterface @@ -157,6 +159,12 @@ func (c *Context) RegisterChatterInterface(ci chatterinterface.ChatterInterface) c.Chatter.Init() } +// RegisterDataCacheInterface registers datacache interface in application +func (c *Context) RegisterDataCacheInterface(di datacacheinterface.DataCacheInterface) { + c.DataCache = di + c.DataCache.Init() +} + // RegisterSquaderInterface registers squader interface in application func (c *Context) RegisterSquaderInterface(si squaderinterface.SquaderInterface) { c.Squader = si diff --git a/lib/chatter/restricters.go b/lib/chatter/restricters.go index f8e1061..9f449b4 100644 --- a/lib/chatter/restricters.go +++ b/lib/chatter/restricters.go @@ -4,8 +4,8 @@ package chatter import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" "strconv" "strings" ) @@ -31,12 +31,13 @@ func (ct *Chatter) userPrivilegesCheck(update *tgbotapi.Update, user *tgbotapi.U } } - playerRaw, ok := c.Users.GetOrCreatePlayer(user.ID) - if !ok { + playerRaw, err := c.DataCache.GetPlayerByTelegramID(user.ID) + if err != nil { + c.Log.Error(err.Error()) return false } - if c.Users.PlayerBetterThan(&playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { return true } @@ -51,7 +52,7 @@ func (ct *Chatter) userPrivilegesCheck(update *tgbotapi.Update, user *tgbotapi.U return true } default: - availableChatsForUser, _ := c.Squader.GetAvailableSquadChatsForUser(&playerRaw) + availableChatsForUser, _ := c.Squader.GetAvailableSquadChatsForUser(playerRaw) for i := range availableChatsForUser { if update.Message.Chat.ID == availableChatsForUser[i].TelegramID { return true diff --git a/lib/config/config.go b/lib/config/config.go index 18b5441..dc0add6 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -4,9 +4,9 @@ package config import ( + "bitbucket.org/pztrn/mogrus" "gopkg.in/yaml.v2" "io/ioutil" - "lab.pztrn.name/golibs/mogrus" "path/filepath" ) diff --git a/lib/connections/connections.go b/lib/connections/connections.go index b2bf0f8..421f708 100644 --- a/lib/connections/connections.go +++ b/lib/connections/connections.go @@ -4,11 +4,11 @@ package connections import ( + "bitbucket.org/pztrn/mogrus" + "git.wtfteam.pro/fat0troll/i2_bot/lib/config" _ "github.com/go-sql-driver/mysql" "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/jmoiron/sqlx" - "git.wtfteam.pro/fat0troll/i2_bot/lib/config" - "lab.pztrn.name/golibs/mogrus" ) // BotInit initializes connection to Telegram diff --git a/lib/datacache/datacacheinterface/datacacheinterface.go b/lib/datacache/datacacheinterface/datacacheinterface.go new file mode 100644 index 0000000..323d22f --- /dev/null +++ b/lib/datacache/datacacheinterface/datacacheinterface.go @@ -0,0 +1,34 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacacheinterface + +import ( + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" +) + +// DataCacheInterface implements DataCache for importing via appcontext. +type DataCacheInterface interface { + Init() + + AddPlayer(player *dbmapping.Player) (int, error) + GetOrCreatePlayerByTelegramID(telegramID int) (*dbmapping.Player, error) + GetPlayerByID(playerID int) (*dbmapping.Player, error) + GetPlayerByTelegramID(telegramID int) (*dbmapping.Player, error) + UpdatePlayerFields(player *dbmapping.Player) (*dbmapping.Player, error) + UpdatePlayerTimestamp(playerID int) error + + AddProfile(profile *dbmapping.Profile) (int, error) + GetPlayersWithCurrentProfiles() map[int]*dbmapping.PlayerProfile + GetProfileByID(profileID int) (*dbmapping.Profile, error) + GetProfileByPlayerID(playerID int) (*dbmapping.Profile, error) + + AddPokememe(pokememeData map[string]string, pokememeLocations map[string]string, pokememeElements map[string]string) (int, error) + GetAllPokememes() map[int]*dbmapping.PokememeFull + GetPokememeByID(pokememeID int) (*dbmapping.PokememeFull, error) + GetPokememeByName(name string) (*dbmapping.PokememeFull, error) + DeletePokememeByID(pokememeID int) error + + GetLeagueBySymbol(symbol string) (*dbmapping.League, error) + GetWeaponTypeByName(name string) (*dbmapping.Weapon, error) +} diff --git a/lib/datacache/elements.go b/lib/datacache/elements.go new file mode 100644 index 0000000..cc29d75 --- /dev/null +++ b/lib/datacache/elements.go @@ -0,0 +1,45 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initElements() { + c.Log.Info("Initializing Elements storage...") + dc.elements = make(map[int]*dbmapping.Element) +} + +func (dc *DataCache) loadElements() { + c.Log.Info("Load current Elements data from database to DataCache...") + elements := []dbmapping.Element{} + err := c.Db.Select(&elements, "SELECT * FROM elements") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.elementsMutex.Lock() + for i := range elements { + dc.elements[elements[i].ID] = &elements[i] + } + c.Log.Info("Loaded elements in DataCache: " + strconv.Itoa(len(dc.elements))) + dc.elementsMutex.Unlock() +} + +func (dc *DataCache) findElementIDBySymbol(symbol string) (int, error) { + dc.elementsMutex.Lock() + for i := range dc.elements { + if dc.elements[i].Symbol == symbol { + dc.elementsMutex.Unlock() + return i, nil + } + } + + dc.elementsMutex.Unlock() + return 0, errors.New("There is no element with symbol = " + symbol) +} diff --git a/lib/datacache/exported.go b/lib/datacache/exported.go new file mode 100644 index 0000000..499947e --- /dev/null +++ b/lib/datacache/exported.go @@ -0,0 +1,76 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "git.wtfteam.pro/fat0troll/i2_bot/lib/appcontext" + "git.wtfteam.pro/fat0troll/i2_bot/lib/datacache/datacacheinterface" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "sync" +) + +var ( + c *appcontext.Context +) + +// DataCache is a function-handling struct for package datacache. +// Also, it's a data cache: it handles all data, powered by DataCache functions. +type DataCache struct { + // Players — users of bot + players map[int]*dbmapping.Player + playersMutex sync.Mutex + // Profiles - game profiles, no matter, actual or not + profiles map[int]*dbmapping.Profile + profilesMutex sync.Mutex + // Current profiles - actual profiles for players, mostly used by bot + // Note: int in this array for player ID, not for profile ID + currentProfiles map[int]*dbmapping.Profile + currentProfilesMutex sync.Mutex + // Pokememes + pokememes map[int]*dbmapping.Pokememe + pokememesMutex sync.Mutex + // Pokememes with all supported data + fullPokememes map[int]*dbmapping.PokememeFull + fullPokememesMutex sync.Mutex + + // Elements + elements map[int]*dbmapping.Element + elementsMutex sync.Mutex + // Leagues + leagues map[int]*dbmapping.League + leaguesMutex sync.Mutex + // Locations + locations map[int]*dbmapping.Location + locationsMutex sync.Mutex + // Weapons + weapons map[int]*dbmapping.Weapon + weaponsMutex sync.Mutex +} + +// New is an initialization function for appcontext +func New(ac *appcontext.Context) { + c = ac + dc := &DataCache{} + c.RegisterDataCacheInterface(datacacheinterface.DataCacheInterface(dc)) +} + +// Init is a initialization function for package +func (dc *DataCache) Init() { + c.Log.Info("Initializing DataCache...") + + dc.initElements() + dc.loadElements() + dc.initLeagues() + dc.loadLeagues() + dc.initLocations() + dc.loadLocations() + dc.initWeapons() + dc.loadWeapons() + dc.initPokememes() + dc.loadPokememes() + dc.initPlayers() + dc.loadPlayers() + dc.initProfiles() + dc.loadProfiles() +} diff --git a/lib/datacache/leagues.go b/lib/datacache/leagues.go new file mode 100644 index 0000000..86e22f8 --- /dev/null +++ b/lib/datacache/leagues.go @@ -0,0 +1,48 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initLeagues() { + c.Log.Info("Initializing Leagues storage...") + dc.leagues = make(map[int]*dbmapping.League) +} + +func (dc *DataCache) loadLeagues() { + c.Log.Info("Load current Leagues data from database to DataCache...") + leagues := []dbmapping.League{} + err := c.Db.Select(&leagues, "SELECT * FROM leagues") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.leaguesMutex.Lock() + for i := range leagues { + dc.leagues[leagues[i].ID] = &leagues[i] + } + c.Log.Info("Loaded leagues in DataCache: " + strconv.Itoa(len(dc.leagues))) + dc.leaguesMutex.Unlock() +} + +// External functions + +// GetLeagueBySymbol returns league from datacache by emoji +func (dc *DataCache) GetLeagueBySymbol(symbol string) (*dbmapping.League, error) { + dc.leaguesMutex.Lock() + for i := range dc.leagues { + if dc.leagues[i].Symbol == symbol { + dc.leaguesMutex.Unlock() + return dc.leagues[i], nil + } + } + + dc.leaguesMutex.Unlock() + return nil, errors.New("There is no league with symbol = " + symbol) +} diff --git a/lib/datacache/locations.go b/lib/datacache/locations.go new file mode 100644 index 0000000..e6d52e3 --- /dev/null +++ b/lib/datacache/locations.go @@ -0,0 +1,45 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initLocations() { + c.Log.Info("Initializing Locations storage...") + dc.locations = make(map[int]*dbmapping.Location) +} + +func (dc *DataCache) loadLocations() { + c.Log.Info("Load current Locations data from database to DataCache...") + locations := []dbmapping.Location{} + err := c.Db.Select(&locations, "SELECT * FROM locations") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.locationsMutex.Lock() + for i := range locations { + dc.locations[locations[i].ID] = &locations[i] + } + c.Log.Info("Loaded locations in DataCache: " + strconv.Itoa(len(dc.locations))) + dc.locationsMutex.Unlock() +} + +func (dc *DataCache) findLocationIDByName(name string) (int, error) { + dc.locationsMutex.Lock() + for i := range dc.locations { + if dc.locations[i].Name == name { + dc.locationsMutex.Unlock() + return i, nil + } + } + + dc.locationsMutex.Unlock() + return 0, errors.New("There is no location with name = " + name) +} diff --git a/lib/datacache/players.go b/lib/datacache/players.go new file mode 100644 index 0000000..f085b26 --- /dev/null +++ b/lib/datacache/players.go @@ -0,0 +1,157 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" + "time" +) + +func (dc *DataCache) initPlayers() { + c.Log.Info("Initializing Players storage...") + dc.players = make(map[int]*dbmapping.Player) +} + +func (dc *DataCache) loadPlayers() { + c.Log.Info("Load current Players data from database to DataCache...") + players := []dbmapping.Player{} + err := c.Db.Select(&players, "SELECT * FROM players") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.playersMutex.Lock() + for i := range players { + dc.players[players[i].ID] = &players[i] + } + c.Log.Info("Loaded players in DataCache: " + strconv.Itoa(len(dc.players))) + dc.playersMutex.Unlock() +} + +// External functions + +// AddPlayer creates new player in database +func (dc *DataCache) AddPlayer(player *dbmapping.Player) (int, error) { + c.Log.Info("DataCache: Creating new user...") + _, err := c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &player) + if err != nil { + c.Log.Error(err.Error()) + return 0, err + } + + insertedPlayer := dbmapping.Player{} + err = c.Db.Get(&insertedPlayer, c.Db.Rebind("SELECT * FROM players WHERE telegram_id=:telegram_id, league_id=:league_id, status=:status, created_at=:created_at, updated_at=:updated_at"), &player) + if err != nil { + c.Log.Error(err.Error()) + return 0, err + } + + dc.playersMutex.Lock() + dc.players[insertedPlayer.ID] = &insertedPlayer + dc.playersMutex.Unlock() + + return insertedPlayer.ID, nil +} + +// GetOrCreatePlayerByTelegramID finds user by Telegram ID and creates one if not exist +func (dc *DataCache) GetOrCreatePlayerByTelegramID(telegramID int) (*dbmapping.Player, error) { + c.Log.Info("DataCache: Getting player with Telegram ID=", telegramID) + + var player *dbmapping.Player + dc.playersMutex.Lock() + for i := range dc.players { + if dc.players[i].TelegramID == telegramID { + player = dc.players[i] + } + } + dc.playersMutex.Unlock() + + if player == nil { + c.Log.Info("There is no such user, creating one...") + newPlayer := dbmapping.Player{} + newPlayer.TelegramID = telegramID + newPlayer.LeagueID = 0 + newPlayer.Status = "nobody" + newPlayer.CreatedAt = time.Now().UTC() + newPlayer.UpdatedAt = time.Now().UTC() + + newPlayerID, err := dc.AddPlayer(&newPlayer) + if err != nil { + return nil, err + } + + player = dc.players[newPlayerID] + } + + return player, nil +} + +// GetPlayerByID returns player from datacache by ID +func (dc *DataCache) GetPlayerByID(playerID int) (*dbmapping.Player, error) { + c.Log.Info("DataCache: Getting player with ID=", playerID) + if dc.players[playerID] != nil { + return dc.players[playerID], nil + } + + return nil, errors.New("There is no user with ID = " + strconv.Itoa(playerID)) +} + +// GetPlayerByTelegramID returns player with such Telegram ID +func (dc *DataCache) GetPlayerByTelegramID(telegramID int) (*dbmapping.Player, error) { + c.Log.Info("DataCache: Getting player with Telegram ID=", telegramID) + + var player *dbmapping.Player + dc.playersMutex.Lock() + for i := range dc.players { + if dc.players[i].TelegramID == telegramID { + player = dc.players[i] + } + } + dc.playersMutex.Unlock() + + if player != nil { + return player, nil + } + + return nil, errors.New("There is no user with Telegram ID = " + strconv.Itoa(telegramID)) +} + +// UpdatePlayerFields writes new fields to player +func (dc *DataCache) UpdatePlayerFields(player *dbmapping.Player) (*dbmapping.Player, error) { + if dc.players[player.ID] != nil { + _, err := c.Db.NamedExec("UPDATE `players` SET league_id=:league_id, status=:status WHERE id=:id", player) + if err != nil { + c.Log.Error(err.Error()) + return dc.players[player.ID], err + } + dc.playersMutex.Lock() + dc.players[player.ID].LeagueID = player.LeagueID + dc.players[player.ID].Status = player.Status + dc.playersMutex.Unlock() + return dc.players[player.ID], nil + } + + return nil, errors.New("There is no user with ID = " + strconv.Itoa(player.ID)) +} + +// UpdatePlayerTimestamp writes current update time to player +func (dc *DataCache) UpdatePlayerTimestamp(playerID int) error { + if dc.players[playerID] != nil { + dc.playersMutex.Lock() + dc.players[playerID].UpdatedAt = time.Now().UTC() + _, err := c.Db.NamedExec("UPDATE `players` SET updated_at=:updated_at WHERE id=:id", dc.players[playerID]) + if err != nil { + c.Log.Error(err.Error()) + dc.playersMutex.Unlock() + return err + } + dc.playersMutex.Unlock() + return nil + } + + return errors.New("There is no user with ID = " + strconv.Itoa(playerID)) +} diff --git a/lib/datacache/pokememes.go b/lib/datacache/pokememes.go new file mode 100644 index 0000000..afde9eb --- /dev/null +++ b/lib/datacache/pokememes.go @@ -0,0 +1,297 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "sort" + "strconv" + "strings" + "time" +) + +func (dc *DataCache) initPokememes() { + c.Log.Info("Initializing Pokememes storage...") + dc.pokememes = make(map[int]*dbmapping.Pokememe) + dc.fullPokememes = make(map[int]*dbmapping.PokememeFull) +} + +func (dc *DataCache) loadPokememes() { + c.Log.Info("Load current Pokememes data from database to DataCache...") + pokememes := []dbmapping.Pokememe{} + err := c.Db.Select(&pokememes, "SELECT * FROM pokememes") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + pokememesElements := []dbmapping.PokememeElement{} + err = c.Db.Select(&pokememesElements, "SELECT * FROM pokememes_elements") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err) + } + pokememesLocations := []dbmapping.PokememeLocation{} + err = c.Db.Select(&pokememesLocations, "SELECT * FROM pokememes_locations") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err) + } + + dc.pokememesMutex.Lock() + dc.fullPokememesMutex.Lock() + for i := range pokememes { + dc.pokememes[pokememes[i].ID] = &pokememes[i] + + // Filling fullPokememes + fullPokememe := dbmapping.PokememeFull{} + elementsListed := []dbmapping.Element{} + locationsListed := []dbmapping.Location{} + + for j := range pokememesLocations { + if pokememesLocations[j].PokememeID == pokememes[i].ID { + for l := range dc.locations { + if pokememesLocations[j].LocationID == dc.locations[l].ID { + locationsListed = append(locationsListed, *dc.locations[l]) + } + } + } + } + + for k := range pokememesElements { + if pokememesElements[k].PokememeID == pokememes[i].ID { + for e := range dc.elements { + if pokememesElements[k].ElementID == dc.elements[e].ID { + elementsListed = append(elementsListed, *dc.elements[e]) + } + } + } + } + + fullPokememe.Pokememe = pokememes[i] + fullPokememe.Elements = elementsListed + fullPokememe.Locations = locationsListed + + dc.fullPokememes[pokememes[i].ID] = &fullPokememe + } + c.Log.Info("Loaded pokememes with all additional information in DataCache: " + strconv.Itoa(len(dc.fullPokememes))) + dc.pokememesMutex.Unlock() + dc.fullPokememesMutex.Unlock() +} + +// External functions + +// AddPokememe adds pokememe from parser +func (dc *DataCache) AddPokememe(pokememeData map[string]string, pokememeLocations map[string]string, pokememeElements map[string]string) (int, error) { + _, noerr := dc.GetPokememeByName(pokememeData["name"]) + if noerr == nil { + return 0, errors.New("This pokememe already exists") + } + + gradeInt := c.Statistics.GetPoints(pokememeData["grade"]) + attackInt := c.Statistics.GetPoints(pokememeData["attack"]) + hpInt := c.Statistics.GetPoints(pokememeData["hp"]) + mpInt := c.Statistics.GetPoints(pokememeData["mp"]) + defenceInt := attackInt + if pokememeData["defence"] != "" { + defenceInt = c.Statistics.GetPoints(pokememeData["defence"]) + } + priceInt := c.Statistics.GetPoints(pokememeData["price"]) + creatorID := c.Statistics.GetPoints(pokememeData["creator_id"]) + + if !(gradeInt != 0 && attackInt != 0 && hpInt != 0 && mpInt != 0 && defenceInt != 0 && priceInt != 0 && creatorID != 0) { + return 0, errors.New("Some of the required numerical values are empty") + } + + pokememe := dbmapping.Pokememe{} + pokememe.Grade = gradeInt + pokememe.Name = pokememeData["name"] + pokememe.Description = pokememeData["description"] + pokememe.Attack = attackInt + pokememe.HP = hpInt + pokememe.MP = mpInt + pokememe.Defence = defenceInt + pokememe.Price = priceInt + if pokememeData["purchaseable"] == "true" { + pokememe.Purchaseable = true + } else { + pokememe.Purchaseable = false + } + pokememe.ImageURL = pokememeData["image"] + pokememe.PlayerID = creatorID + pokememe.CreatedAt = time.Now().UTC() + + locations := []dbmapping.Location{} + elements := []dbmapping.Element{} + + for i := range pokememeLocations { + locationID, err := dc.findLocationIDByName(pokememeLocations[i]) + if err != nil { + return 0, err + } + locations = append(locations, *dc.locations[locationID]) + } + + for i := range pokememeElements { + elementID, err := dc.findElementIDBySymbol(pokememeElements[i]) + if err != nil { + return 0, err + } + elements = append(elements, *dc.elements[elementID]) + } + + // All objects are prepared, let's fill database with it! + c.Log.Debug("Filling pokememe...") + _, err := c.Db.NamedExec("INSERT INTO pokememes VALUES(NULL, :grade, :name, :description, :attack, :hp, :mp, :defence, :price, :purchaseable, :image_url, :player_id, :created_at)", &pokememe) + if err != nil { + return 0, err + } + + c.Log.Debug("Finding newly added pokememe...") + insertedPokememe := dbmapping.Pokememe{} + err = c.Db.Get(&insertedPokememe, c.Db.Rebind("SELECT * FROM pokememes WHERE grade=? AND name=?"), pokememe.Grade, pokememe.Name) + if err != nil { + return 0, err + } + + // Now we creating locations and elements links + locationsAndElementsFilledSuccessfully := true + c.Log.Debug("Filling locations...") + for i := range locations { + link := dbmapping.PokememeLocation{} + link.PokememeID = insertedPokememe.ID + link.LocationID = locations[i].ID + link.CreatedAt = time.Now().UTC() + + _, err := c.Db.NamedExec("INSERT INTO pokememes_locations VALUES(NULL, :pokememe_id, :location_id, :created_at)", &link) + if err != nil { + c.Log.Error(err.Error()) + locationsAndElementsFilledSuccessfully = false + } + } + + c.Log.Debug("Filling elements...") + for i := range elements { + link := dbmapping.PokememeElement{} + link.PokememeID = insertedPokememe.ID + link.ElementID = elements[i].ID + link.CreatedAt = time.Now().UTC() + + _, err := c.Db.NamedExec("INSERT INTO pokememes_elements VALUES(NULL, :pokememe_id, :element_id, :created_at)", &link) + if err != nil { + c.Log.Error(err.Error()) + locationsAndElementsFilledSuccessfully = false + } + } + + if !locationsAndElementsFilledSuccessfully { + c.Log.Debug("All fucked up, removing what we have already added...") + // There is something fucked up. In normal state we're should never reach this code + _, err = c.Db.NamedExec("DELETE FROM pokememes_locations WHERE pokememe_id=:id", &insertedPokememe) + if err != nil { + c.Log.Error(err.Error()) + } + _, err = c.Db.NamedExec("DELETE FROM pokememes_elements WHERE pokememe_id=:id", &insertedPokememe) + if err != nil { + c.Log.Error(err.Error()) + } + _, err = c.Db.NamedExec("DELETE FROM pokememes where id=:id", &insertedPokememe) + if err != nil { + c.Log.Error(err.Error()) + } + return 0, errors.New("Failed to add pokememe to database") + } + + fullPokememe := dbmapping.PokememeFull{} + fullPokememe.Pokememe = insertedPokememe + fullPokememe.Locations = locations + fullPokememe.Elements = elements + + // Filling data cache + dc.pokememesMutex.Lock() + dc.fullPokememesMutex.Lock() + dc.pokememes[insertedPokememe.ID] = &insertedPokememe + dc.fullPokememes[insertedPokememe.ID] = &fullPokememe + dc.pokememesMutex.Unlock() + dc.fullPokememesMutex.Unlock() + + return insertedPokememe.ID, nil +} + +// GetAllPokememes returns all pokememes +// Index in resulted map counts all pokememes ordered by grade and alphabetically +func (dc *DataCache) GetAllPokememes() map[int]*dbmapping.PokememeFull { + pokememes := make(map[int]*dbmapping.PokememeFull) + dc.fullPokememesMutex.Lock() + + var keys []string + keysToIDs := make(map[string]int) + for i := range dc.fullPokememes { + keys = append(keys, strconv.Itoa(dc.fullPokememes[i].Pokememe.Grade)+"_"+dc.fullPokememes[i].Pokememe.Name) + keysToIDs[strconv.Itoa(dc.fullPokememes[i].Pokememe.Grade)+"_"+dc.fullPokememes[i].Pokememe.Name] = i + } + sort.Strings(keys) + + idx := 0 + for _, k := range keys { + pokememes[idx] = dc.fullPokememes[keysToIDs[k]] + idx++ + } + + dc.fullPokememesMutex.Unlock() + return pokememes +} + +// GetPokememeByID returns pokememe with additional information by ID +func (dc *DataCache) GetPokememeByID(pokememeID int) (*dbmapping.PokememeFull, error) { + dc.fullPokememesMutex.Lock() + if dc.fullPokememes[pokememeID] != nil { + dc.fullPokememesMutex.Unlock() + return dc.fullPokememes[pokememeID], nil + } + dc.fullPokememesMutex.Unlock() + return nil, errors.New("There is no pokememe with ID = " + strconv.Itoa(pokememeID)) +} + +// GetPokememeByName returns pokememe from datacache by name +func (dc *DataCache) GetPokememeByName(name string) (*dbmapping.PokememeFull, error) { + dc.fullPokememesMutex.Lock() + for i := range dc.fullPokememes { + if strings.HasPrefix(dc.fullPokememes[i].Pokememe.Name, name) { + dc.fullPokememesMutex.Unlock() + return dc.fullPokememes[i], nil + } + } + dc.fullPokememesMutex.Unlock() + return nil, errors.New("There is no pokememe with name = " + name) +} + +// DeletePokememeByID removes pokememe from database +func (dc *DataCache) DeletePokememeByID(pokememeID int) error { + pokememe, err := dc.GetPokememeByID(pokememeID) + if err != nil { + return err + } + + _, err = c.Db.NamedExec("DELETE FROM pokememes_locations WHERE pokememe_id=:id", &pokememe.Pokememe) + if err != nil { + c.Log.Error(err.Error()) + } + _, err = c.Db.NamedExec("DELETE FROM pokememes_elements WHERE pokememe_id=:id", &pokememe.Pokememe) + if err != nil { + c.Log.Error(err.Error()) + } + _, err = c.Db.NamedExec("DELETE FROM pokememes where id=:id", &pokememe.Pokememe) + if err != nil { + c.Log.Error(err.Error()) + } + + dc.pokememesMutex.Lock() + dc.fullPokememesMutex.Lock() + delete(dc.pokememes, pokememe.Pokememe.ID) + delete(dc.fullPokememes, pokememe.Pokememe.ID) + dc.pokememesMutex.Unlock() + dc.fullPokememesMutex.Unlock() + return nil +} diff --git a/lib/datacache/profiles.go b/lib/datacache/profiles.go new file mode 100644 index 0000000..acce95f --- /dev/null +++ b/lib/datacache/profiles.go @@ -0,0 +1,118 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initProfiles() { + c.Log.Info("Initializing Profiles storage...") + dc.profiles = make(map[int]*dbmapping.Profile) + dc.currentProfiles = make(map[int]*dbmapping.Profile) +} + +func (dc *DataCache) loadProfiles() { + c.Log.Info("Load current Profiles data from database to DataCache...") + profiles := []dbmapping.Profile{} + err := c.Db.Select(&profiles, "SELECT * FROM profiles") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.profilesMutex.Lock() + dc.currentProfilesMutex.Lock() + for i := range profiles { + dc.profiles[profiles[i].ID] = &profiles[i] + + // Filling current profiles + if dc.currentProfiles[profiles[i].PlayerID] != nil { + if dc.currentProfiles[profiles[i].PlayerID].CreatedAt.Before(profiles[i].CreatedAt) { + dc.currentProfiles[profiles[i].PlayerID] = &profiles[i] + } + } + + dc.currentProfiles[profiles[i].PlayerID] = &profiles[i] + } + c.Log.Info("Loaded profiles in DataCache: " + strconv.Itoa(len(dc.profiles))) + c.Log.Info("Loaded current profiles in DataCache: " + strconv.Itoa(len(dc.currentProfiles))) + dc.profilesMutex.Unlock() + dc.currentProfilesMutex.Unlock() +} + +// External functions + +// AddProfile creates new profile in database +func (dc *DataCache) AddProfile(profile *dbmapping.Profile) (int, error) { + _, err := c.Db.NamedExec("INSERT INTO `profiles` VALUES(NULL, :player_id, :nickname, :telegram_nickname, :level_id, :pokeballs, :wealth, :pokememes_wealth, :exp, :egg_exp, :power, :weapon_id, :crystalls, :created_at)", &profile) + if err != nil { + c.Log.Error(err.Error()) + return 0, err + } + insertedProfile := dbmapping.Profile{} + err = c.Db.Get(&insertedProfile, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? AND created_at=?"), profile.PlayerID, profile.CreatedAt) + if err != nil { + c.Log.Error(err.Error()) + return 0, err + } + + dc.profilesMutex.Lock() + dc.currentProfilesMutex.Lock() + dc.profiles[insertedProfile.ID] = &insertedProfile + dc.currentProfiles[insertedProfile.PlayerID] = &insertedProfile + dc.currentProfilesMutex.Unlock() + dc.profilesMutex.Unlock() + + return insertedProfile.ID, nil +} + +// GetPlayersWithCurrentProfiles returns users with profiles, if exist +func (dc *DataCache) GetPlayersWithCurrentProfiles() map[int]*dbmapping.PlayerProfile { + dc.currentProfilesMutex.Lock() + dc.playersMutex.Lock() + + users := make(map[int]*dbmapping.PlayerProfile) + + for i := range dc.players { + user := dbmapping.PlayerProfile{} + user.Player = *dc.players[i] + if dc.players[i].LeagueID != 0 { + user.League = *dc.leagues[dc.players[i].LeagueID] + } + + if dc.currentProfiles[dc.players[i].ID] != nil { + user.HaveProfile = true + user.Profile = *dc.currentProfiles[dc.players[i].ID] + } else { + user.HaveProfile = false + } + + users[dc.players[i].ID] = &user + } + + dc.currentProfilesMutex.Unlock() + dc.playersMutex.Unlock() + return users +} + +// GetProfileByID returns profile from datacache by ID +func (dc *DataCache) GetProfileByID(profileID int) (*dbmapping.Profile, error) { + if dc.profiles[profileID] != nil { + return dc.profiles[profileID], nil + } + + return nil, errors.New("There is no profile with ID = " + strconv.Itoa(profileID)) +} + +// GetProfileByPlayerID returns current profile for player +func (dc *DataCache) GetProfileByPlayerID(playerID int) (*dbmapping.Profile, error) { + if dc.currentProfiles[playerID] != nil { + return dc.currentProfiles[playerID], nil + } + + return nil, errors.New("There is no profile for user with ID = " + strconv.Itoa(playerID)) +} diff --git a/lib/datacache/weapons.go b/lib/datacache/weapons.go new file mode 100644 index 0000000..7c7214d --- /dev/null +++ b/lib/datacache/weapons.go @@ -0,0 +1,48 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initWeapons() { + c.Log.Info("Initializing Weapons storage...") + dc.weapons = make(map[int]*dbmapping.Weapon) +} + +func (dc *DataCache) loadWeapons() { + c.Log.Info("Load current Weapons data from database to DataCache...") + weapons := []dbmapping.Weapon{} + err := c.Db.Select(&weapons, "SELECT * FROM weapons") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.weaponsMutex.Lock() + for i := range weapons { + dc.weapons[weapons[i].ID] = &weapons[i] + } + c.Log.Info("Loaded weapon types in DataCache: " + strconv.Itoa(len(dc.weapons))) + dc.weaponsMutex.Unlock() +} + +// External functions + +// GetWeaponTypeByName returns weapon type from datacache by weapon name +func (dc *DataCache) GetWeaponTypeByName(name string) (*dbmapping.Weapon, error) { + dc.weaponsMutex.Lock() + for i := range dc.weapons { + if dc.weapons[i].Name == name { + dc.weaponsMutex.Unlock() + return dc.weapons[i], nil + } + } + + dc.weaponsMutex.Unlock() + return nil, errors.New("There is no weapon type with name = " + name) +} diff --git a/lib/dbmapping/profiles.go b/lib/dbmapping/profiles.go index 80cc99f..9ce1cc7 100644 --- a/lib/dbmapping/profiles.go +++ b/lib/dbmapping/profiles.go @@ -24,24 +24,3 @@ type Profile struct { Crystalls int `db:"crystalls"` CreatedAt time.Time `db:"created_at"` } - -// ProfileWithAddons is a struct, which represents `profiles` table item in database and some good external fields. -type ProfileWithAddons struct { - ID int `db:"id"` - PlayerID int `db:"player_id"` - TelegramID int `db:"telegram_id"` - LeagueID int `db:"league_id"` - LeagueSymbol string `db:"league_symbol"` - Nickname string `db:"nickname"` - TelegramNickname string `db:"telegram_nickname"` - LevelID int `db:"level_id"` - Pokeballs int `db:"pokeballs"` - Wealth int `db:"wealth"` - PokememesWealth int `db:"pokememes_wealth"` - Exp int `db:"exp"` - EggExp int `db:"egg_exp"` - Power int `db:"power"` - WeaponID int `db:"weapon_id"` - Crystalls int `db:"crystalls"` - CreatedAt time.Time `db:"created_at"` -} diff --git a/lib/pokedexer/cleaners.go b/lib/pokedexer/cleaners.go index e12cb40..80dab6a 100644 --- a/lib/pokedexer/cleaners.go +++ b/lib/pokedexer/cleaners.go @@ -15,32 +15,12 @@ func (p *Pokedexer) DeletePokememe(update *tgbotapi.Update) string { return "fail" } - pokememe, ok := p.GetPokememeByID(strconv.Itoa(pokememeNum)) - if !ok { - return "fail" - } - - _, err := c.Db.NamedExec("DELETE FROM pokememes WHERE id=:id", &pokememe.Pokememe) + err := c.DataCache.DeletePokememeByID(pokememeNum) if err != nil { c.Log.Error(err.Error()) return "fail" } - _, err = c.Db.NamedExec("DELETE FROM pokememes_elements WHERE pokememe_id=:id", &pokememe.Pokememe) - if err != nil { - c.Log.Debug(err.Error()) - } - - _, err = c.Db.NamedExec("DELETE FROM pokememes_locations WHERE pokememe_id=:id", &pokememe.Pokememe) - if err != nil { - c.Log.Debug(err.Error()) - } - - _, err = c.Db.NamedExec("DELETE FROM profiles_pokememes WHERE pokememe_id=:id", &pokememe.Pokememe) - if err != nil { - c.Log.Debug(err.Error()) - } - message := "Покемем удалён." msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) diff --git a/lib/pokedexer/getters.go b/lib/pokedexer/getters.go index 521a147..1cee1b5 100644 --- a/lib/pokedexer/getters.go +++ b/lib/pokedexer/getters.go @@ -5,184 +5,50 @@ package pokedexer import ( "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" - "strconv" ) -// Internal functions - -func (p *Pokedexer) formFullPokememes(pokememes []dbmapping.Pokememe) ([]dbmapping.PokememeFull, bool) { - pokememesArray := []dbmapping.PokememeFull{} - elements := []dbmapping.Element{} - err := c.Db.Select(&elements, "SELECT * FROM elements") - if err != nil { - c.Log.Error(err) - return pokememesArray, false - } - locations := []dbmapping.Location{} - err = c.Db.Select(&locations, "SELECT * FROM locations") - if err != nil { - c.Log.Error(err) - return pokememesArray, false - } - pokememesElements := []dbmapping.PokememeElement{} - err = c.Db.Select(&pokememesElements, "SELECT * FROM pokememes_elements") - if err != nil { - c.Log.Error(err) - return pokememesArray, false - } - pokememesLocations := []dbmapping.PokememeLocation{} - err = c.Db.Select(&pokememesLocations, "SELECT * FROM pokememes_locations") - if err != nil { - c.Log.Error(err) - return pokememesArray, false - } - - for i := range pokememes { - fullPokememe := dbmapping.PokememeFull{} - elementsListed := []dbmapping.Element{} - locationsListed := []dbmapping.Location{} - - for j := range pokememesLocations { - if pokememesLocations[j].PokememeID == pokememes[i].ID { - for l := range locations { - if pokememesLocations[j].LocationID == locations[l].ID { - locationsListed = append(locationsListed, locations[l]) - } - } - } - } - - for k := range pokememesElements { - if pokememesElements[k].PokememeID == pokememes[i].ID { - for e := range elements { - if pokememesElements[k].ElementID == elements[e].ID { - elementsListed = append(elementsListed, elements[e]) - } - } - } - } - - fullPokememe.Pokememe = pokememes[i] - fullPokememe.Elements = elementsListed - fullPokememe.Locations = locationsListed - - pokememesArray = append(pokememesArray, fullPokememe) - } - - return pokememesArray, true -} - // External functions -// GetPokememes returns all existing pokememes, known by bot -func (p *Pokedexer) GetPokememes() ([]dbmapping.PokememeFull, bool) { - pokememesArray := []dbmapping.PokememeFull{} - pokememes := []dbmapping.Pokememe{} - err := c.Db.Select(&pokememes, "SELECT * FROM pokememes ORDER BY grade asc, name asc") +func (p *Pokedexer) getBestPokememes(playerID int) (map[int]*dbmapping.PokememeFull, bool) { + pokememesArray := make(map[int]*dbmapping.PokememeFull) + + playerRaw, err := c.DataCache.GetPlayerByID(playerID) if err != nil { - c.Log.Error(err) + c.Log.Error(err.Error()) return pokememesArray, false } - - pokememesArray, ok := p.formFullPokememes(pokememes) - return pokememesArray, ok -} - -func (p *Pokedexer) getBestPokememes(playerID int) ([]dbmapping.PokememeFull, bool) { - pokememesArray := []dbmapping.PokememeFull{} - playerRaw, ok := c.Users.GetPlayerByID(playerID) - if !ok { - return pokememesArray, ok - } - profileRaw, ok := c.Users.GetProfile(playerID) - if !ok { - return pokememesArray, ok + profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) + return pokememesArray, false } if playerRaw.LeagueID == 0 { return pokememesArray, false } - // TODO: make it more complicated - pokememes := []dbmapping.Pokememe{} + allPokememes := c.DataCache.GetAllPokememes() if profileRaw.LevelID < 4 { - err := c.Db.Select(&pokememes, c.Db.Rebind("SELECT * FROM pokememes WHERE grade = ? ORDER BY attack DESC"), profileRaw.LevelID+1) - if err != nil { - c.Log.Error(err) - return pokememesArray, false + for i := range allPokememes { + if allPokememes[i].Pokememe.Grade == profileRaw.LevelID+1 { + pokememesArray[allPokememes[i].Pokememe.Attack] = allPokememes[i] + } } } else { - err := c.Db.Select(&pokememes, c.Db.Rebind("SELECT p.* FROM pokememes p, pokememes_elements pe, elements e WHERE e.league_id = ? AND p.grade = ? AND pe.element_id = e.id AND pe.pokememe_id = p.id ORDER BY p.attack DESC"), playerRaw.LeagueID, profileRaw.LevelID+1) - if err != nil { - c.Log.Error(err) - return pokememesArray, false - } - } - - pokememesArray, ok = p.formFullPokememes(pokememes) - return pokememesArray, ok -} - -// GetPokememeByID returns single pokememe based on internal ID in database -func (p *Pokedexer) GetPokememeByID(pokememeID string) (dbmapping.PokememeFull, bool) { - fullPokememe := dbmapping.PokememeFull{} - pokememe := dbmapping.Pokememe{} - err := c.Db.Get(&pokememe, c.Db.Rebind("SELECT * FROM pokememes WHERE id=?"), pokememeID) - if err != nil { - c.Log.Error(err) - return fullPokememe, false - } - elements := []dbmapping.Element{} - err = c.Db.Select(&elements, "SELECT * FROM elements") - if err != nil { - c.Log.Error(err) - return fullPokememe, false - } - locations := []dbmapping.Location{} - err = c.Db.Select(&locations, "SELECT * FROM locations") - if err != nil { - c.Log.Error(err) - return fullPokememe, false - } - pokememesElements := []dbmapping.PokememeElement{} - err = c.Db.Select(&pokememesElements, "SELECT * FROM pokememes_elements WHERE pokememe_id='"+strconv.Itoa(pokememe.ID)+"'") - if err != nil { - c.Log.Error(err) - return fullPokememe, false - } - pokememesLocations := []dbmapping.PokememeLocation{} - err = c.Db.Select(&pokememesLocations, "SELECT * FROM pokememes_locations WHERE pokememe_id='"+strconv.Itoa(pokememe.ID)+"'") - if err != nil { - c.Log.Error(err) - return fullPokememe, false - } - - elementsListed := []dbmapping.Element{} - locationsListed := []dbmapping.Location{} - - for j := range pokememesLocations { - if pokememesLocations[j].PokememeID == pokememe.ID { - for l := range locations { - if pokememesLocations[j].LocationID == locations[l].ID { - locationsListed = append(locationsListed, locations[l]) + for i := range allPokememes { + if allPokememes[i].Pokememe.Grade == profileRaw.LevelID+1 { + matchLeague := false + for j := range allPokememes[i].Elements { + if allPokememes[i].Elements[j].LeagueID == playerRaw.LeagueID { + matchLeague = true + } + } + if matchLeague { + pokememesArray[allPokememes[i].Pokememe.Attack] = allPokememes[i] } } } } - for k := range pokememesElements { - if pokememesElements[k].PokememeID == pokememe.ID { - for e := range elements { - if pokememesElements[k].ElementID == elements[e].ID { - elementsListed = append(elementsListed, elements[e]) - } - } - } - } - - fullPokememe.Pokememe = pokememe - fullPokememe.Elements = elementsListed - fullPokememe.Locations = locationsListed - - return fullPokememe, true + return pokememesArray, true } diff --git a/lib/pokedexer/parsers.go b/lib/pokedexer/parsers.go index 8a409e7..c1bfe7e 100644 --- a/lib/pokedexer/parsers.go +++ b/lib/pokedexer/parsers.go @@ -4,248 +4,143 @@ package pokedexer import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" "regexp" "strconv" "strings" - "time" + // "time" ) // ParsePokememe parses pokememe, forwarded from PokememeBroBot, to database func (p *Pokedexer) ParsePokememe(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - text := update.Message.Text - var defendablePokememe = false - pokememeStringsArray := strings.Split(text, "\n") + pokememeStringsArray := strings.Split(update.Message.Text, "\n") pokememeRunesArray := make([][]rune, 0) for i := range pokememeStringsArray { pokememeRunesArray = append(pokememeRunesArray, []rune(pokememeStringsArray[i])) } - if len(pokememeRunesArray) == 13 { - defendablePokememe = true + pokememeData := make(map[string]string) + pokememeLocations := make(map[string]string) + pokememeElements := make(map[string]string) + + hitPointsRx := regexp.MustCompile("(\\d|\\.)+(K|M)?") + + for i := range pokememeStringsArray { + c.Log.Debug("Processing string: " + pokememeStringsArray[i]) + if strings.Contains(pokememeStringsArray[i], "⃣") { + // Strings with name and grade + pokememeData["grade"] = string(pokememeRunesArray[i][0]) + pokememeData["name"] = string(pokememeRunesArray[i][3:]) + } + + if i == 1 { + pokememeData["description"] = string(pokememeRunesArray[i]) + } + + if strings.HasPrefix(pokememeStringsArray[i], "Обитает: ") { + // Elements + locationsString := strings.TrimPrefix(pokememeStringsArray[i], "Обитает: ") + locationsArray := strings.Split(locationsString, ", ") + for i := range locationsArray { + pokememeLocations[strconv.Itoa(i)] = locationsArray[i] + } + } + + if strings.HasPrefix(pokememeStringsArray[i], "Элементы: ") { + // Elements + elementsString := strings.TrimPrefix(pokememeStringsArray[i], "Элементы: ") + elementsArray := strings.Split(elementsString, " ") + for i := range elementsArray { + pokememeElements[strconv.Itoa(i)] = elementsArray[i] + } + } + + if strings.HasPrefix(pokememeStringsArray[i], "⚔Атака: ") { + // Attack, HP, MP + hitPoints := hitPointsRx.FindAllString(string(pokememeRunesArray[i]), -1) + if len(hitPoints) != 3 { + c.Log.Error("Can't parse hitpoints!") + c.Log.Debug("Points string was: " + string(pokememeRunesArray[i])) + p.pokememeAddFailureMessage(update) + return "fail" + } + pokememeData["attack"] = hitPoints[0] + pokememeData["hp"] = hitPoints[1] + pokememeData["mp"] = hitPoints[2] + } + + if strings.HasPrefix(pokememeStringsArray[i], "🛡Защита ") { + // Defence for top-level pokememes + defence := hitPointsRx.FindAllString(string(pokememeRunesArray[i]), -1) + if len(defence) != 1 { + c.Log.Error("Can't parse defence!") + c.Log.Debug("Defence string was: " + string(pokememeRunesArray[i])) + p.pokememeAddFailureMessage(update) + return "fail" + } + pokememeData["defence"] = defence[0] + } + + if strings.HasPrefix(pokememeStringsArray[i], "Стоимость :") { + // Price + price := hitPointsRx.FindAllString(string(pokememeRunesArray[i]), -1) + if len(price) != 1 { + c.Log.Error("Can't parse price!") + c.Log.Debug("Price string was: " + string(pokememeRunesArray[i])) + p.pokememeAddFailureMessage(update) + return "fail" + } + pokememeData["price"] = price[0] + } + + if strings.HasPrefix(pokememeStringsArray[i], "Купить: ") { + // Purchaseability + pokememeData["purchaseable"] = "false" + if strings.Contains(pokememeStringsArray[i], "Можно") { + pokememeData["purchaseable"] = "true" + } + } } - // Getting elements - elements := []dbmapping.Element{} - elementEmojis := make([]string, 0) - elementEmojis = append(elementEmojis, string(pokememeRunesArray[4][11])) - if len(pokememeRunesArray[4]) > 12 { - elementEmojis = append(elementEmojis, string(pokememeRunesArray[4][13])) - } - if len(pokememeRunesArray[4]) > 14 { - elementEmojis = append(elementEmojis, string(pokememeRunesArray[4][15])) + // Image + for _, entity := range *update.Message.Entities { + if entity.Type == "text_link" && entity.URL != "" { + pokememeData["image"] = entity.URL + } } - err := c.Db.Select(&elements, "SELECT * FROM elements WHERE symbol IN ('"+strings.Join(elementEmojis, "', '")+"')") + // Checking grade to be integer + _, err := strconv.Atoi(pokememeData["grade"]) if err != nil { c.Log.Error(err.Error()) p.pokememeAddFailureMessage(update) return "fail" } - // Getting hit-points - hitPointsRx := regexp.MustCompile("(\\d|\\.)+(K|M)?") - hitPoints := hitPointsRx.FindAllString(string(pokememeRunesArray[5]), -1) - if len(hitPoints) != 3 { - c.Log.Error("Can't parse hitpoints!") - c.Log.Debug(pokememeRunesArray[5]) + pokememeData["creator_id"] = strconv.Itoa(playerRaw.ID) + + c.Log.Debugln("Pokememe data: ", pokememeData) + c.Log.Debugln("Elements: ", pokememeElements) + c.Log.Debugln("Locations: ", pokememeLocations) + + if len(pokememeElements) == 0 { p.pokememeAddFailureMessage(update) return "fail" } - defence := "0" - price := "0" - - locations := []dbmapping.Location{} - - purchaseable := false - image := "" - - if defendablePokememe { - // Actions for high-grade pokememes - defenceMatch := hitPointsRx.FindAllString(string(pokememeRunesArray[6]), -1) - if len(defenceMatch) < 1 { - c.Log.Error("Can't parse defence!") - c.Log.Debug(pokememeRunesArray[6]) - p.pokememeAddFailureMessage(update) - return "fail" - } - defence = defenceMatch[0] - priceMatch := hitPointsRx.FindAllString(string(pokememeRunesArray[7]), -1) - if len(priceMatch) < 1 { - c.Log.Error("Can't parse price!") - c.Log.Debug(pokememeRunesArray[7]) - p.pokememeAddFailureMessage(update) - return "fail" - } - price = priceMatch[0] - locationsPrepare := strings.Split(string(pokememeRunesArray[8]), ": ") - if len(locationsPrepare) < 2 { - c.Log.Error("Can't parse locations!") - c.Log.Debug(pokememeRunesArray[8]) - p.pokememeAddFailureMessage(update) - return "fail" - } - locationsNames := strings.Split(locationsPrepare[1], ", ") - if len(locationsNames) < 1 { - c.Log.Error("Can't parse locations!") - c.Log.Debug(locationsPrepare) - p.pokememeAddFailureMessage(update) - return "fail" - } - - err2 := c.Db.Select(&locations, "SELECT * FROM locations WHERE name IN ('"+strings.Join(locationsNames, "', '")+"')") - if err2 != nil { - c.Log.Error(err2.Error()) - p.pokememeAddFailureMessage(update) - return "fail" - } - if strings.HasSuffix(string(pokememeRunesArray[9]), "Можно") { - purchaseable = true - } - image = strings.Replace(string(pokememeRunesArray[12]), " ", "", -1) - } else { - // Actions for low-grade pokememes - defence = hitPoints[0] - priceMatch := hitPointsRx.FindAllString(string(pokememeRunesArray[6]), -1) - if len(priceMatch) < 1 { - c.Log.Error("Can't parse price!") - c.Log.Debug(pokememeRunesArray[6]) - p.pokememeAddFailureMessage(update) - return "fail" - } - price = priceMatch[0] - locationsPrepare := strings.Split(string(pokememeRunesArray[7]), ": ") - if len(locationsPrepare) < 2 { - c.Log.Error("Can't parse locations!") - c.Log.Debug(pokememeRunesArray[7]) - p.pokememeAddFailureMessage(update) - return "fail" - } - locationsNames := strings.Split(locationsPrepare[1], ", ") - if len(locationsNames) < 1 { - c.Log.Error("Can't parse locations!") - c.Log.Debug(locationsPrepare) - p.pokememeAddFailureMessage(update) - return "fail" - } - - err2 := c.Db.Select(&locations, "SELECT * FROM locations WHERE name IN ('"+strings.Join(locationsNames, "', '")+"')") - if err2 != nil { - c.Log.Error(err2.Error()) - p.pokememeAddFailureMessage(update) - return "fail" - } - if strings.HasSuffix(string(pokememeRunesArray[8]), "Можно") { - purchaseable = true - } - image = strings.Replace(string(pokememeRunesArray[11]), " ", "", -1) - } - - grade := string(pokememeRunesArray[0][0]) - name := string(pokememeRunesArray[0][3:]) - description := string(pokememeRunesArray[1]) - c.Log.Debug("Pokememe grade: " + grade) - c.Log.Debug("Pokememe name: " + name) - c.Log.Debug("Pokememe description: " + description) - c.Log.Debug("Elements:") - for i := range elements { - c.Log.Debug(elements[i].Symbol + " " + elements[i].Name) - } - c.Log.Debug("Attack: " + hitPoints[0]) - c.Log.Debug("HP: " + hitPoints[1]) - c.Log.Debug("MP: " + hitPoints[2]) - c.Log.Debug("Defence: " + defence) - c.Log.Debug("Price: " + price) - c.Log.Debug("Locations:") - for i := range locations { - c.Log.Debug(locations[i].Symbol + " " + locations[i].Name) - } - if purchaseable { - c.Log.Debug("Purchaseable") - } else { - c.Log.Debug("Non-purchaseable") - } - c.Log.Debug("Image: " + image) - - // Building pokememe - pokememe := dbmapping.Pokememe{} - // Checking if pokememe exists in database - err3 := c.Db.Get(&pokememe, c.Db.Rebind("SELECT * FROM pokememes WHERE grade='"+grade+"' AND name='"+name+"';")) - if err3 != nil { - c.Log.Debug("Adding new pokememe...") - } else { - c.Log.Info("This pokememe already exist. Return specific error.") - p.pokememeAddDuplicateMessage(update) - return "dup" - } - - gradeInt, _ := strconv.Atoi(grade) - attackInt := c.Statistics.GetPoints(hitPoints[0]) - hpInt := c.Statistics.GetPoints(hitPoints[1]) - mpInt := c.Statistics.GetPoints(hitPoints[2]) - defenceInt := c.Statistics.GetPoints(defence) - priceInt := c.Statistics.GetPoints(price) - - pokememe.Grade = gradeInt - pokememe.Name = name - pokememe.Description = description - pokememe.Attack = attackInt - pokememe.HP = hpInt - pokememe.MP = mpInt - pokememe.Defence = defenceInt - pokememe.Price = priceInt - if purchaseable { - pokememe.Purchaseable = true - } else { - pokememe.Purchaseable = false - } - pokememe.ImageURL = image - pokememe.PlayerID = playerRaw.ID - pokememe.CreatedAt = time.Now().UTC() - - _, err4 := c.Db.NamedExec("INSERT INTO pokememes VALUES(NULL, :grade, :name, :description, :attack, :hp, :mp, :defence, :price, :purchaseable, :image_url, :player_id, :created_at)", &pokememe) - if err4 != nil { - c.Log.Error(err4.Error()) + if len(pokememeLocations) == 0 { p.pokememeAddFailureMessage(update) return "fail" } - // Getting new pokememe - err5 := c.Db.Get(&pokememe, c.Db.Rebind("SELECT * FROM pokememes WHERE grade='"+grade+"' AND name='"+name+"';")) - if err5 != nil { - c.Log.Error("Pokememe isn't added!") + newPokememeID, err := c.DataCache.AddPokememe(pokememeData, pokememeLocations, pokememeElements) + if err != nil { + c.Log.Error(err.Error()) p.pokememeAddFailureMessage(update) return "fail" } - for i := range elements { - link := dbmapping.PokememeElement{} - link.PokememeID = pokememe.ID - link.ElementID = elements[i].ID - link.CreatedAt = time.Now().UTC() - _, err6 := c.Db.NamedExec("INSERT INTO pokememes_elements VALUES(NULL, :pokememe_id, :element_id, :created_at)", &link) - if err6 != nil { - c.Log.Error(err6.Error()) - p.pokememeAddFailureMessage(update) - return "fail" - } - } - for i := range locations { - link := dbmapping.PokememeLocation{} - link.PokememeID = pokememe.ID - link.LocationID = locations[i].ID - link.CreatedAt = time.Now().UTC() - - _, err7 := c.Db.NamedExec("INSERT INTO pokememes_locations VALUES(NULL, :pokememe_id, :location_id, :created_at)", &link) - if err7 != nil { - c.Log.Error(err7.Error()) - p.pokememeAddFailureMessage(update) - return "fail" - } - } - - p.pokememeAddSuccessMessage(update) + p.pokememeAddSuccessMessage(update, newPokememeID) return "ok" } diff --git a/lib/pokedexer/pokedexer.go b/lib/pokedexer/pokedexer.go index 0edd2d1..15d9b12 100644 --- a/lib/pokedexer/pokedexer.go +++ b/lib/pokedexer/pokedexer.go @@ -4,15 +4,16 @@ package pokedexer import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" + "sort" "strconv" ) -func (p *Pokedexer) pokememesListing(update *tgbotapi.Update, page int, pokememesArray []dbmapping.PokememeFull) { +func (p *Pokedexer) pokememesListing(update *tgbotapi.Update, page int, pokememesArray map[int]*dbmapping.PokememeFull) { message := "*Известные боту покемемы*\n" message += "Список отсортирован по грейду и алфавиту.\n" - message += "Покедекс: " + strconv.Itoa(len(pokememesArray)) + " / 249\n" + message += "Покедекс: " + strconv.Itoa(len(pokememesArray)) + " / 274\n" message += "Отображаем покемемов с " + strconv.Itoa(((page-1)*50)+1) + " по " + strconv.Itoa(page*50) + "\n" if len(pokememesArray) > page*50 { message += "Переход на следующую страницу: /pokedeks" + strconv.Itoa(page+1) @@ -22,7 +23,13 @@ func (p *Pokedexer) pokememesListing(update *tgbotapi.Update, page int, pokememe } message += "\n\n" + var keys []int for i := range pokememesArray { + keys = append(keys, i) + } + sort.Ints(keys) + + for _, i := range keys { if (i+1 > 50*(page-1)) && (i+1 < (50*page)+1) { pk := pokememesArray[i].Pokememe pkE := pokememesArray[i].Elements @@ -52,9 +59,10 @@ func (p *Pokedexer) pokememesListing(update *tgbotapi.Update, page int, pokememe c.Bot.Send(msg) } -func (p *Pokedexer) pokememeAddSuccessMessage(update *tgbotapi.Update) { +func (p *Pokedexer) pokememeAddSuccessMessage(update *tgbotapi.Update, newPokememeID int) { message := "*Покемем успешно добавлен.*\n\n" - message += "Посмотреть всех известных боту покемемов можно командой /pokedeks" + message += "Посмотреть всех известных боту покемемов можно командой /pokedeks\n" + message += "Посмотреть свежедобавленного покемема можно командой /pk" + strconv.Itoa(newPokememeID) msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) msg.ParseMode = "Markdown" diff --git a/lib/pokedexer/pokedexerinterface/pokedexerinterface.go b/lib/pokedexer/pokedexerinterface/pokedexerinterface.go index 1037136..6a5b3ee 100644 --- a/lib/pokedexer/pokedexerinterface/pokedexerinterface.go +++ b/lib/pokedexer/pokedexerinterface/pokedexerinterface.go @@ -4,8 +4,8 @@ package pokedexerinterface import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" ) // PokedexerInterface implements Pokedexer for importing via appcontext. @@ -16,8 +16,5 @@ type PokedexerInterface interface { PokememeInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player) string BestPokememesList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - GetPokememes() ([]dbmapping.PokememeFull, bool) - GetPokememeByID(pokememeID string) (dbmapping.PokememeFull, bool) - DeletePokememe(update *tgbotapi.Update) string } diff --git a/lib/pokedexer/responders.go b/lib/pokedexer/responders.go index 7233c9f..41368f1 100644 --- a/lib/pokedexer/responders.go +++ b/lib/pokedexer/responders.go @@ -4,8 +4,9 @@ package pokedexer import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" + "sort" "strconv" "strings" ) @@ -18,8 +19,14 @@ func (p *Pokedexer) BestPokememesList(update *tgbotapi.Update, playerRaw *dbmapp return "fail" } - message := "*Лучшие покемемы для ловли*\n\n" + var attacks []int for i := range pokememes { + attacks = append(attacks, i) + } + sort.Sort(sort.Reverse(sort.IntSlice(attacks))) + + message := "*Лучшие покемемы для ловли*\n\n" + for _, i := range attacks { pk := pokememes[i].Pokememe pkL := pokememes[i].Locations pkE := pokememes[i].Elements @@ -63,25 +70,24 @@ func (p *Pokedexer) PokememesList(update *tgbotapi.Update) { if page == 0 { page = 1 } - pokememesArray, ok := p.GetPokememes() - if !ok { - c.Talkers.BotError(update) - } else { - p.pokememesListing(update, page, pokememesArray) - } + pokememesArray := c.DataCache.GetAllPokememes() + p.pokememesListing(update, page, pokememesArray) } // PokememeInfo shows information about single pokememe based on internal ID func (p *Pokedexer) PokememeInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { pokememeNumber := strings.Replace(update.Message.Text, "/pk", "", 1) var calculatePossibilites = true - profileRaw, ok := c.Users.GetProfile(playerRaw.ID) - if !ok { + profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) calculatePossibilites = false } - pokememe, ok := p.GetPokememeByID(pokememeNumber) - if !ok { + pokememeID, _ := strconv.Atoi(pokememeNumber) + pokememe, err := c.DataCache.GetPokememeByID(pokememeID) + if err != nil { + c.Log.Error(err.Error()) return "fail" } diff --git a/lib/router/callback_request.go b/lib/router/callback_request.go index 2a71e36..b43ed56 100644 --- a/lib/router/callback_request.go +++ b/lib/router/callback_request.go @@ -10,8 +10,9 @@ import ( // RouteCallback routes inline requests to bot func (r *Router) RouteCallback(update *tgbotapi.Update) string { - playerRaw, ok := c.Users.GetOrCreatePlayer(update.CallbackQuery.From.ID) - if !ok { + playerRaw, err := c.DataCache.GetOrCreatePlayerByTelegramID(update.CallbackQuery.From.ID) + if err != nil { + c.Log.Error(err.Error()) return "fail" } @@ -20,9 +21,9 @@ func (r *Router) RouteCallback(update *tgbotapi.Update) string { switch { case enableAlarmCallback.MatchString(update.CallbackQuery.Data): - return c.Reminder.CreateAlarmSetting(update, &playerRaw) + return c.Reminder.CreateAlarmSetting(update, playerRaw) case disableAlarmCallback.MatchString(update.CallbackQuery.Data): - return c.Reminder.DestroyAlarmSetting(update, &playerRaw) + return c.Reminder.DestroyAlarmSetting(update, playerRaw) } return "ok" diff --git a/lib/router/inline.go b/lib/router/inline.go index a330e01..dc28f2f 100644 --- a/lib/router/inline.go +++ b/lib/router/inline.go @@ -11,8 +11,9 @@ import ( // RouteInline routes inline requests to bot func (r *Router) RouteInline(update *tgbotapi.Update) string { - playerRaw, ok := c.Users.GetOrCreatePlayer(update.InlineQuery.From.ID) - if !ok { + playerRaw, err := c.DataCache.GetOrCreatePlayerByTelegramID(update.InlineQuery.From.ID) + if err != nil { + c.Log.Error(err.Error()) return "fail" } @@ -73,7 +74,7 @@ func (r *Router) RouteInline(update *tgbotapi.Update) string { Results: results, } - _, err := c.Bot.AnswerInlineQuery(inlineConf) + _, err = c.Bot.AnswerInlineQuery(inlineConf) if err != nil { c.Log.Error(err.Error()) } diff --git a/lib/router/router.go b/lib/router/router.go index 67bb8b3..fccacb5 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -9,8 +9,9 @@ import ( // RouteRequest decides, what to do with user input func (r *Router) RouteRequest(update *tgbotapi.Update) string { - playerRaw, ok := c.Users.GetOrCreatePlayer(update.Message.From.ID) - if !ok { + playerRaw, err := c.DataCache.GetOrCreatePlayerByTelegramID(update.Message.From.ID) + if err != nil { + c.Log.Error(err.Error()) // Silently fail return "fail" } @@ -20,13 +21,10 @@ func (r *Router) RouteRequest(update *tgbotapi.Update) string { return "fail" } - c.Log.Debug("Received message from chat ") - c.Log.Debugln(chatRaw.TelegramID) - if update.Message.Chat.IsGroup() || update.Message.Chat.IsSuperGroup() { - return r.routeGroupRequest(update, &playerRaw, &chatRaw) + return r.routeGroupRequest(update, playerRaw, &chatRaw) } else if update.Message.Chat.IsPrivate() { - return r.routePrivateRequest(update, &playerRaw, &chatRaw) + return r.routePrivateRequest(update, playerRaw, &chatRaw) } return "ok" diff --git a/lib/squader/getters.go b/lib/squader/getters.go index 09065a7..47d58de 100644 --- a/lib/squader/getters.go +++ b/lib/squader/getters.go @@ -144,13 +144,13 @@ func (s *Squader) GetUserRolesInSquads(playerRaw *dbmapping.Player) ([]dbmapping for i := range userRolesRaw { userRoleFull := dbmapping.SquadPlayerFull{} userRoleFull.Player = *playerRaw - userProfile, profileOk := c.Users.GetProfile(playerRaw.ID) - userRoleFull.Profile = userProfile + userProfile, profileError := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + userRoleFull.Profile = *userProfile userRoleFull.UserRole = userRolesRaw[i].UserType squad, squadOk := s.GetSquadByID(userRolesRaw[i].SquadID) userRoleFull.Squad = squad - if profileOk && squadOk { + if profileError == nil && squadOk { userRoles = append(userRoles, userRoleFull) } } diff --git a/lib/squader/restricters.go b/lib/squader/restricters.go index bf51294..55a1cb6 100644 --- a/lib/squader/restricters.go +++ b/lib/squader/restricters.go @@ -4,16 +4,17 @@ package squader import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" ) // CleanFlood will clean flood from squads func (s *Squader) CleanFlood(update *tgbotapi.Update, chatRaw *dbmapping.Chat) string { switch s.IsChatASquadEnabled(chatRaw) { case "main": - talker, ok := c.Users.GetOrCreatePlayer(update.Message.From.ID) - if !ok { + talker, err := c.DataCache.GetPlayerByTelegramID(update.Message.From.ID) + if err != nil { + c.Log.Error(err.Error()) s.deleteFloodMessage(update) return "fail" } diff --git a/lib/squader/squader.go b/lib/squader/squader.go index 411dd13..f7782ca 100644 --- a/lib/squader/squader.go +++ b/lib/squader/squader.go @@ -4,8 +4,8 @@ package squader import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" "regexp" "strconv" "strings" @@ -38,8 +38,8 @@ func (s *Squader) getPlayersForSquad(squadID int) ([]dbmapping.SquadPlayerFull, for ii := range squadPlayers { if squadPlayers[ii].PlayerID == playersRaw[i].ID { playerWithProfile := dbmapping.SquadPlayerFull{} - profile, _ := c.Users.GetProfile(playersRaw[i].ID) - playerWithProfile.Profile = profile + profile, _ := c.DataCache.GetProfileByPlayerID(playersRaw[i].ID) + playerWithProfile.Profile = *profile playerWithProfile.Player = playersRaw[i] playerWithProfile.Squad = squad playerWithProfile.UserRole = squadPlayers[ii].UserType @@ -114,8 +114,9 @@ func (s *Squader) createSquad(update *tgbotapi.Update, chatID int, floodChatID i if err != nil { c.Log.Debug(err) - playerRaw, ok := c.Users.GetOrCreatePlayer(update.Message.From.ID) - if !ok { + playerRaw, err := c.DataCache.GetPlayerByTelegramID(update.Message.From.ID) + if err != nil { + c.Log.Error(err.Error()) return squad, "fail" } @@ -287,20 +288,22 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl return s.squadUserAdditionFailure(update) } - playerRaw, ok := c.Users.GetPlayerByID(playerID) - if !ok { + playerRaw, err := c.DataCache.GetPlayerByID(playerID) + if err != nil { + c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) } squadRaw := dbmapping.Squad{} - err := c.Db.Get(&squadRaw, c.Db.Rebind("SELECT * FROM squads WHERE id=?"), squadID) + err = c.Db.Get(&squadRaw, c.Db.Rebind("SELECT * FROM squads WHERE id=?"), squadID) if err != nil { c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) } - if !c.Users.PlayerBetterThan(&playerRaw, "admin") { - _, ok = c.Users.GetProfile(playerRaw.ID) - if !ok { + if !c.Users.PlayerBetterThan(playerRaw, "admin") { + _, err = c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) } } @@ -315,7 +318,7 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl } } - if !c.Users.PlayerBetterThan(&playerRaw, "admin") { + if !c.Users.PlayerBetterThan(playerRaw, "admin") { if playerRaw.LeagueID != 1 { return s.squadUserAdditionFailure(update) } diff --git a/lib/statistics/squads.go b/lib/statistics/squads.go index 6ae0379..2d293ec 100644 --- a/lib/statistics/squads.go +++ b/lib/statistics/squads.go @@ -27,12 +27,18 @@ func (s *Statistics) SquadStatictics(squadID int) string { for i := range squadMembers { fullInfo := dbmapping.SquadPlayerFull{} - playerRaw, _ := c.Users.GetPlayerByID(squadMembers[i].PlayerID) - profileRaw, _ := c.Users.GetProfile(playerRaw.ID) + playerRaw, err := c.DataCache.GetPlayerByID(squadMembers[i].PlayerID) + if err != nil { + c.Log.Error(err.Error()) + } + profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) + } fullInfo.Squad = squad - fullInfo.Player = playerRaw - fullInfo.Profile = profileRaw + fullInfo.Player = *playerRaw + fullInfo.Profile = *profileRaw squadMembersWithInformation = append(squadMembersWithInformation, fullInfo) } diff --git a/lib/users/getters.go b/lib/users/getters.go index 3daf102..873e119 100644 --- a/lib/users/getters.go +++ b/lib/users/getters.go @@ -4,62 +4,10 @@ package users import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" - "time" + "github.com/go-telegram-bot-api/telegram-bot-api" ) -// GetProfile returns last saved profile of player -func (u *Users) GetProfile(playerID int) (dbmapping.Profile, bool) { - profileRaw := dbmapping.Profile{} - err := c.Db.Get(&profileRaw, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? ORDER BY created_at DESC LIMIT 1"), playerID) - if err != nil { - c.Log.Error(err) - return profileRaw, false - } - - return profileRaw, true -} - -// GetPlayerByID returns dbmapping.Player instance with given ID. -func (u *Users) GetPlayerByID(playerID int) (dbmapping.Player, bool) { - playerRaw := dbmapping.Player{} - err := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE id=?"), playerID) - if err != nil { - c.Log.Error(err.Error()) - return playerRaw, false - } - - return playerRaw, true -} - -// GetOrCreatePlayer seeks for player in database via Telegram ID. -// In case, when there is no player with such ID, new player will be created. -func (u *Users) GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) { - playerRaw := dbmapping.Player{} - err := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE telegram_id=? ORDER BY created_at desc LIMIT 1"), telegramID) - if err != nil { - c.Log.Error("Message user not found in database.") - c.Log.Error(err.Error()) - - // Create "nobody" user - playerRaw.TelegramID = telegramID - playerRaw.LeagueID = 0 - playerRaw.Status = "nobody" - playerRaw.CreatedAt = time.Now().UTC() - playerRaw.UpdatedAt = time.Now().UTC() - _, err = c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &playerRaw) - if err != nil { - c.Log.Error(err.Error()) - return playerRaw, false - } - } else { - c.Log.Debug("Message user found in database.") - } - - return playerRaw, true -} - // GetPrettyName returns "pretty" name of user (first_name + last name or username) func (u *Users) GetPrettyName(user *tgbotapi.User) string { userName := user.FirstName diff --git a/lib/users/parsers.go b/lib/users/parsers.go index 6310aa5..1205b73 100644 --- a/lib/users/parsers.go +++ b/lib/users/parsers.go @@ -15,15 +15,14 @@ import ( // Internal functions func (u *Users) fillProfilePokememe(profileID int, meme string, attack string, rarity string) { - spkRaw := dbmapping.Pokememe{} - err := c.Db.Get(&spkRaw, c.Db.Rebind("SELECT * FROM pokememes WHERE name='"+meme+"';")) + spkRaw, err := c.DataCache.GetPokememeByName(meme) if err != nil { c.Log.Error(err.Error()) } else { attackInt := c.Statistics.GetPoints(attack) ppk := dbmapping.ProfilePokememe{} ppk.ProfileID = profileID - ppk.PokememeID = spkRaw.ID + ppk.PokememeID = spkRaw.Pokememe.ID ppk.PokememeAttack = attackInt ppk.PokememeRarity = rarity ppk.CreatedAt = time.Now().UTC() @@ -75,12 +74,13 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe currentString := string(profileRunesArray[i]) currentRunes := profileRunesArray[i] if strings.HasPrefix(currentString, "🈸") || strings.HasPrefix(currentString, "🈳 ") || strings.HasPrefix(currentString, "🈵") { - err1 := c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE symbol='"+string(currentRunes[0])+"'")) - if err1 != nil { - c.Log.Error(err1.Error()) + leagueRaw, err := c.DataCache.GetLeagueBySymbol(string(currentRunes[0])) + if err != nil { + c.Log.Error(err.Error()) u.profileAddFailureMessage(update) return "fail" } + league = *leagueRaw for j := range currentRunes { if j > 1 { nickname += string(currentRunes[j]) @@ -209,10 +209,9 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe } // Information is gathered, let's create profile in database! - weaponRaw := dbmapping.Weapon{} - err2 := c.Db.Get(&weaponRaw, c.Db.Rebind("SELECT * FROM weapons WHERE name='"+weapon+"'")) - if err2 != nil { - c.Log.Error(err2.Error()) + weaponRaw, err := c.DataCache.GetWeaponTypeByName(weapon) + if err != nil { + c.Log.Error(err.Error()) } if playerRaw.LeagueID == 0 { @@ -221,26 +220,17 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe if playerRaw.Status == "nobody" { playerRaw.Status = "common" } - _, err4 := c.Db.NamedExec("UPDATE `players` SET league_id=:league_id, status=:status WHERE id=:id", &playerRaw) - if err4 != nil { - c.Log.Error(err4.Error()) + _, err = c.DataCache.UpdatePlayerFields(playerRaw) + if err != nil { u.profileAddFailureMessage(update) return "fail" } } else if playerRaw.LeagueID != league.ID { - // Duplicate profile: user changed league, beware! + // User changed league, beware! playerRaw.LeagueID = league.ID playerRaw.Status = "league_changed" - playerRaw.CreatedAt = time.Now().UTC() - _, err5 := c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &playerRaw) - if err5 != nil { - c.Log.Error(err5.Error()) - u.profileAddFailureMessage(update) - return "fail" - } - err6 := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE telegram_id='"+strconv.Itoa(playerRaw.TelegramID)+"' AND league_id='"+strconv.Itoa(league.ID)+"';")) - if err6 != nil { - c.Log.Error(err6.Error()) + _, err = c.DataCache.UpdatePlayerFields(playerRaw) + if err != nil { u.profileAddFailureMessage(update) return "fail" } @@ -261,25 +251,22 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe profileRaw.Crystalls = crystallsInt profileRaw.CreatedAt = time.Now().UTC() - _, err3 := c.Db.NamedExec("INSERT INTO `profiles` VALUES(NULL, :player_id, :nickname, :telegram_nickname, :level_id, :pokeballs, :wealth, :pokememes_wealth, :exp, :egg_exp, :power, :weapon_id, :crystalls, :created_at)", &profileRaw) - if err3 != nil { - c.Log.Error(err3.Error()) + newProfileID, err := c.DataCache.AddProfile(&profileRaw) + if err != nil { + c.Log.Error(err.Error()) u.profileAddFailureMessage(update) return "fail" } - err8 := c.Db.Get(&profileRaw, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? AND created_at=?"), profileRaw.PlayerID, profileRaw.CreatedAt) - if err8 != nil { - c.Log.Error(err8.Error()) - c.Log.Error("Profile isn't added!") + _, err = c.DataCache.GetProfileByID(newProfileID) + if err != nil { + c.Log.Error(err.Error()) u.profileAddFailureMessage(update) return "fail" } - playerRaw.UpdatedAt = time.Now().UTC() - _, err7 := c.Db.NamedExec("UPDATE `players` SET updated_at=:updated_at WHERE id=:id", &playerRaw) - if err7 != nil { - c.Log.Error(err7.Error()) + err = c.DataCache.UpdatePlayerTimestamp(playerRaw.ID) + if err != nil { u.profileAddFailureMessage(update) return "fail" } @@ -304,11 +291,11 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe rarity = "super liber" meme = strings.Replace(meme, "🔷", "", 1) } - if strings.HasPrefix(meme, "❄️") { + if strings.HasPrefix(meme, "❄") { rarity = "new year" - meme = strings.Replace(meme, "❄️", "", 1) + meme = strings.Replace(meme, "❄", "", 1) } - u.fillProfilePokememe(profileRaw.ID, meme, attack, rarity) + u.fillProfilePokememe(newProfileID, meme, attack, rarity) } u.profileAddSuccessMessage(update, league.ID, profileRaw.LevelID) diff --git a/lib/users/responders.go b/lib/users/responders.go index 02c0c75..e6bb4d7 100644 --- a/lib/users/responders.go +++ b/lib/users/responders.go @@ -29,12 +29,9 @@ func (u *Users) FindByLevel(update *tgbotapi.Update) string { return "fail" } - usersArray, ok := u.findUsersByLevel(levelID) - if !ok { - return "fail" - } + users := u.findUsersByLevel(levelID) - u.foundUsersMessage(update, usersArray) + u.foundUsersMessage(update, users) return "ok" } @@ -47,12 +44,9 @@ func (u *Users) FindByName(update *tgbotapi.Update) string { return "fail" } - usersArray, ok := u.findUserByName(commandArgs) - if !ok { - return "fail" - } + users := u.findUserByName(commandArgs) - u.foundUsersMessage(update, usersArray) + u.foundUsersMessage(update, users) return "ok" } @@ -66,20 +60,22 @@ func (u *Users) ForeignProfileMessage(update *tgbotapi.Update) string { return "fail" } - playerRaw, ok := u.GetPlayerByID(userID) - if !ok { + playerRaw, err := c.DataCache.GetPlayerByID(userID) + if err != nil { + c.Log.Error(err.Error()) return "fail" } - _, ok = u.GetProfile(playerRaw.ID) - if !ok { + _, err = c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) return c.Talkers.BotError(update) } - return u.ProfileMessage(update, &playerRaw) + return u.ProfileMessage(update, playerRaw) } -// ProfileAddEffectsMesage shows when user tries to post profile with effects enabled +// ProfileAddEffectsMessage shows when user tries to post profile with effects enabled func (u *Users) ProfileAddEffectsMessage(update *tgbotapi.Update) string { message := "*Наркоман, штоле?*\n\n" message += "Бот не принимает профили во время активированных эффектов. Закончи свои дела и принеси чистый профиль через полчаса." @@ -94,12 +90,13 @@ func (u *Users) ProfileAddEffectsMessage(update *tgbotapi.Update) string { // ProfileMessage shows current player's profile func (u *Users) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - profileRaw, ok := u.GetProfile(playerRaw.ID) - if !ok { + profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) return c.Talkers.AnyMessageUnauthorized(update) } league := dbmapping.League{} - err := c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), playerRaw.LeagueID) + err = c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), playerRaw.LeagueID) if err != nil { c.Log.Error(err) } @@ -210,11 +207,8 @@ func (u *Users) UsersList(update *tgbotapi.Update) string { if page == 0 { page = 1 } - usersArray, ok := u.getUsersWithProfiles() - if !ok { - return c.Talkers.BotError(update) - } + users := c.DataCache.GetPlayersWithCurrentProfiles() - u.usersList(update, page, usersArray) + u.usersList(update, page, users) return "ok" } diff --git a/lib/users/users.go b/lib/users/users.go index b98ee47..cdaa591 100644 --- a/lib/users/users.go +++ b/lib/users/users.go @@ -6,84 +6,70 @@ package users import ( "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" + "sort" "strconv" + "strings" + "time" ) // Internal functions for Users package -func (u *Users) getUsersWithProfiles() ([]dbmapping.PlayerProfile, bool) { - usersArray := []dbmapping.PlayerProfile{} - players := []dbmapping.Player{} - err := c.Db.Select(&players, "SELECT * FROM players") - if err != nil { - c.Log.Error(err) - return usersArray, false - } +func (u *Users) findUsersByLevel(levelID int) map[int]*dbmapping.PlayerProfile { + selectedUsers := make(map[int]*dbmapping.PlayerProfile) + allUsers := c.DataCache.GetPlayersWithCurrentProfiles() - for i := range players { - playerWithProfile := dbmapping.PlayerProfile{} - profile, ok := u.GetProfile(players[i].ID) - if !ok { - playerWithProfile.HaveProfile = false - } else { - playerWithProfile.HaveProfile = true - } - playerWithProfile.Profile = profile - playerWithProfile.Player = players[i] - - league := dbmapping.League{} - if players[i].LeagueID != 0 { - err = c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), players[i].LeagueID) - if err != nil { - c.Log.Error(err.Error()) - return usersArray, false + for i := range allUsers { + if allUsers[i].Profile.LevelID == levelID { + if allUsers[i].Player.UpdatedAt.After(time.Now().UTC().Add(-72 * time.Hour)) { + selectedUsers[i] = allUsers[i] } } - playerWithProfile.League = league - - usersArray = append(usersArray, playerWithProfile) } - return usersArray, true + return selectedUsers } -func (u *Users) findUsersByLevel(levelID int) ([]dbmapping.ProfileWithAddons, bool) { - selectedUsers := []dbmapping.ProfileWithAddons{} +func (u *Users) findUserByName(pattern string) map[int]*dbmapping.PlayerProfile { + selectedUsers := make(map[int]*dbmapping.PlayerProfile) + allUsers := c.DataCache.GetPlayersWithCurrentProfiles() - err := c.Db.Select(&selectedUsers, c.Db.Rebind("SELECT p.*, l.symbol AS league_symbol, l.id AS league_id, pl.telegram_id FROM players pl, profiles p, leagues l WHERE pl.id = p.player_id AND l.id = pl.league_id AND p.created_at > NOW() - INTERVAL 72 HOUR AND p.level_id = ? GROUP BY player_id"), levelID) - if err != nil { - c.Log.Error(err.Error()) - return selectedUsers, false + for i := range allUsers { + matchedPattern := false + if strings.Contains(strings.ToLower(allUsers[i].Profile.Nickname), strings.ToLower(pattern)) { + matchedPattern = true + } + if strings.Contains(strings.ToLower(allUsers[i].Profile.TelegramNickname), strings.ToLower(pattern)) { + matchedPattern = true + } + if matchedPattern { + selectedUsers[i] = allUsers[i] + } } - return selectedUsers, true + return selectedUsers } -func (u *Users) findUserByName(pattern string) ([]dbmapping.ProfileWithAddons, bool) { - selectedUsers := []dbmapping.ProfileWithAddons{} - - err := c.Db.Select(&selectedUsers, c.Db.Rebind("SELECT * FROM (SELECT p.*, l.symbol AS league_symbol, l.id AS league_id, pl.telegram_id FROM players pl, profiles p, leagues l WHERE p.player_id = pl.id AND l.id = pl.league_id AND (p.nickname LIKE ? OR p.telegram_nickname LIKE ?) ORDER BY p.id DESC LIMIT 100000) AS find_users_table GROUP BY player_id"), "%"+pattern+"%", "%"+pattern+"%") - if err != nil { - c.Log.Error(err.Error()) - return selectedUsers, false +func (u *Users) foundUsersMessage(update *tgbotapi.Update, users map[int]*dbmapping.PlayerProfile) { + var keys []int + for i := range users { + keys = append(keys, i) } + sort.Ints(keys) - return selectedUsers, true -} - -func (u *Users) foundUsersMessage(update *tgbotapi.Update, usersArray []dbmapping.ProfileWithAddons) { message := "*Найденные игроки:*\n" - for i := range usersArray { - message += "#" + strconv.Itoa(usersArray[i].PlayerID) - message += " " + usersArray[i].LeagueSymbol - message += " " + usersArray[i].Nickname - if usersArray[i].TelegramNickname != "" { - message += " (@" + u.FormatUsername(usersArray[i].TelegramNickname) + ")" + for _, i := range keys { + message += "#" + strconv.Itoa(users[i].Player.ID) + if users[i].HaveProfile { + message += " " + users[i].League.Symbol + message += " " + users[i].Profile.Nickname + if users[i].Profile.TelegramNickname != "" { + message += " (@" + u.FormatUsername(users[i].Profile.TelegramNickname) + ")" + } } - message += " /profile" + strconv.Itoa(usersArray[i].PlayerID) + "\n" - message += "Telegram ID: " + strconv.Itoa(usersArray[i].TelegramID) + "\n" - message += "Последнее обновление: " + usersArray[i].CreatedAt.Format("02.01.2006 15:04:05") + "\n" + message += " /profile" + strconv.Itoa(users[i].Player.ID) + "\n" + message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n" + message += "Последнее обновление: " + users[i].Player.CreatedAt.Format("02.01.2006 15:04:05") + "\n" if len(message) > 2000 { msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) @@ -130,12 +116,12 @@ func (u *Users) profileAddFailureMessage(update *tgbotapi.Update) { c.Bot.Send(msg) } -func (u *Users) usersList(update *tgbotapi.Update, page int, usersArray []dbmapping.PlayerProfile) { +func (u *Users) usersList(update *tgbotapi.Update, page int, users map[int]*dbmapping.PlayerProfile) { message := "*Зарегистрированные пользователи бота*\n" message += "Список отсортирован по ID регистрации.\n" - message += "Количество зарегистрированных пользователей: " + strconv.Itoa(len(usersArray)) + "\n" + message += "Количество зарегистрированных пользователей: " + strconv.Itoa(len(users)) + "\n" message += "Отображаем пользователей с " + strconv.Itoa(((page-1)*25)+1) + " по " + strconv.Itoa(page*25) + "\n" - if len(usersArray) > page*25 { + if len(users) > page*25 { message += "Переход на следующую страницу: /users" + strconv.Itoa(page+1) } if page > 1 { @@ -143,30 +129,36 @@ func (u *Users) usersList(update *tgbotapi.Update, page int, usersArray []dbmapp } message += "\n\n" - for i := range usersArray { + var keys []int + for i := range users { + keys = append(keys, i) + } + sort.Ints(keys) + + for _, i := range keys { if (i+1 > 25*(page-1)) && (i+1 < (25*page)+1) { - message += "#" + strconv.Itoa(usersArray[i].Player.ID) - if usersArray[i].HaveProfile { - message += " " + usersArray[i].League.Symbol - message += " " + usersArray[i].Profile.Nickname - if usersArray[i].Profile.TelegramNickname != "" { - message += " (@" + u.FormatUsername(usersArray[i].Profile.TelegramNickname) + ")" + message += "#" + strconv.Itoa(users[i].Player.ID) + if users[i].HaveProfile { + message += " " + users[i].League.Symbol + message += " " + users[i].Profile.Nickname + if users[i].Profile.TelegramNickname != "" { + message += " (@" + u.FormatUsername(users[i].Profile.TelegramNickname) + ")" } - message += " /profile" + strconv.Itoa(usersArray[i].Player.ID) + "\n" - message += "Telegram ID: " + strconv.Itoa(usersArray[i].Player.TelegramID) + "\n" - message += "Последнее обновление: " + usersArray[i].Profile.CreatedAt.Format("02.01.2006 15:04:05") + "\n" + message += " /profile" + strconv.Itoa(users[i].Player.ID) + "\n" + message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n" + message += "Последнее обновление: " + users[i].Profile.CreatedAt.Format("02.01.2006 15:04:05") + "\n" } else { - if usersArray[i].Player.Status == "special" { + if users[i].Player.Status == "special" { message += " _суперюзер_\n" } else { message += " _без профиля_\n" } - message += "Telegram ID: " + strconv.Itoa(usersArray[i].Player.TelegramID) + "\n" + message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n" } } } - if len(usersArray) > page*25 { + if len(users) > page*25 { message += "\n" message += "Переход на следующую страницу: /users" + strconv.Itoa(page+1) } diff --git a/lib/users/usersinterface/usersinterface.go b/lib/users/usersinterface/usersinterface.go index b705ce1..25304cc 100644 --- a/lib/users/usersinterface/usersinterface.go +++ b/lib/users/usersinterface/usersinterface.go @@ -15,9 +15,6 @@ type UsersInterface interface { ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) string GetPrettyName(user *tgbotapi.User) string - GetProfile(playerID int) (dbmapping.Profile, bool) - GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) - GetPlayerByID(playerID int) (dbmapping.Player, bool) PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool FindByLevel(update *tgbotapi.Update) string diff --git a/lib/welcomer/welcomer.go b/lib/welcomer/welcomer.go index 2e68271..e6abb5e 100644 --- a/lib/welcomer/welcomer.go +++ b/lib/welcomer/welcomer.go @@ -9,22 +9,25 @@ import ( ) func (w *Welcomer) groupWelcomeUser(update *tgbotapi.Update, newUser *tgbotapi.User) string { - playerRaw, ok := c.Users.GetOrCreatePlayer(newUser.ID) - if !ok { + playerRaw, err := c.DataCache.GetPlayerByTelegramID(newUser.ID) + if err != nil { + c.Log.Error(err.Error()) return "fail" } - _, profileExist := c.Users.GetProfile(playerRaw.ID) + _, profileExist := c.DataCache.GetProfileByPlayerID(playerRaw.ID) message := "*Бот Инстинкта приветствует тебя, *" message += c.Users.GetPrettyName(newUser) message += "*!*\n\n" - if profileExist { + if profileExist == nil { if playerRaw.LeagueID != 1 { w.alertSpyUser(update, newUser) } } else { + c.Log.Info("Following profile error is OK.") + c.Log.Info(err.Error()) w.alertUserWithoutProfile(update, newUser) }