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.
This commit is contained in:
		
							
								
								
									
										61
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										61
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										44
									
								
								Gopkg.toml
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -4,9 +4,9 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"bitbucket.org/pztrn/mogrus" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"io/ioutil" | ||||
| 	"lab.pztrn.name/golibs/mogrus" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										34
									
								
								lib/datacache/datacacheinterface/datacacheinterface.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/datacache/datacacheinterface/datacacheinterface.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										45
									
								
								lib/datacache/elements.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/datacache/elements.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										76
									
								
								lib/datacache/exported.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/datacache/exported.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
							
								
								
									
										48
									
								
								lib/datacache/leagues.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								lib/datacache/leagues.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										45
									
								
								lib/datacache/locations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/datacache/locations.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										157
									
								
								lib/datacache/players.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								lib/datacache/players.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										297
									
								
								lib/datacache/pokememes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								lib/datacache/pokememes.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										118
									
								
								lib/datacache/profiles.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								lib/datacache/profiles.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										48
									
								
								lib/datacache/weapons.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								lib/datacache/weapons.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
| @@ -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"` | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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()) | ||||
| 	} | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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" | ||||
| 		} | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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" | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user