diff --git a/cmd/i2_bot/i2_bot.go b/cmd/i2_bot/i2_bot.go index cc16b9e..6cbe3d9 100644 --- a/cmd/i2_bot/i2_bot.go +++ b/cmd/i2_bot/i2_bot.go @@ -6,15 +6,17 @@ package main import ( "github.com/go-telegram-bot-api/telegram-bot-api" "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/broadcaster" "lab.pztrn.name/fat0troll/i2_bot/lib/chatter" "lab.pztrn.name/fat0troll/i2_bot/lib/forwarder" - "lab.pztrn.name/fat0troll/i2_bot/lib/getters" "lab.pztrn.name/fat0troll/i2_bot/lib/migrations" - "lab.pztrn.name/fat0troll/i2_bot/lib/parsers" "lab.pztrn.name/fat0troll/i2_bot/lib/pinner" + "lab.pztrn.name/fat0troll/i2_bot/lib/pokedexer" "lab.pztrn.name/fat0troll/i2_bot/lib/router" "lab.pztrn.name/fat0troll/i2_bot/lib/squader" + "lab.pztrn.name/fat0troll/i2_bot/lib/statistics" "lab.pztrn.name/fat0troll/i2_bot/lib/talkers" + "lab.pztrn.name/fat0troll/i2_bot/lib/users" "lab.pztrn.name/fat0troll/i2_bot/lib/welcomer" "time" ) @@ -30,13 +32,15 @@ func main() { migrations.New(c) c.RunDatabaseMigrations() forwarder.New(c) - parsers.New(c) + pokedexer.New(c) pinner.New(c) talkers.New(c) - getters.New(c) + broadcaster.New(c) welcomer.New(c) chatter.New(c) squader.New(c) + users.New(c) + statistics.New(c) c.Log.Info("=======================") c.Log.Info("= i2_bot initialized. =") diff --git a/lib/appcontext/appcontext.go b/lib/appcontext/appcontext.go index a89cf0c..4aee7cf 100644 --- a/lib/appcontext/appcontext.go +++ b/lib/appcontext/appcontext.go @@ -6,17 +6,19 @@ package appcontext import ( "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/jmoiron/sqlx" + "lab.pztrn.name/fat0troll/i2_bot/lib/broadcaster/broadcasterinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/chatter/chatterinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/config" "lab.pztrn.name/fat0troll/i2_bot/lib/connections" "lab.pztrn.name/fat0troll/i2_bot/lib/forwarder/forwarderinterface" - "lab.pztrn.name/fat0troll/i2_bot/lib/getters/gettersinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/migrations/migrationsinterface" - "lab.pztrn.name/fat0troll/i2_bot/lib/parsers/parsersinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/pinner/pinnerinterface" + "lab.pztrn.name/fat0troll/i2_bot/lib/pokedexer/pokedexerinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/router/routerinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/squader/squaderinterface" + "lab.pztrn.name/fat0troll/i2_bot/lib/statistics/statisticsinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/talkers/talkersinterface" + "lab.pztrn.name/fat0troll/i2_bot/lib/users/usersinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/welcomer/welcomerinterface" "lab.pztrn.name/golibs/mogrus" "os" @@ -24,20 +26,22 @@ import ( // Context is an application context struct type Context struct { - Cfg *config.Config - Log *mogrus.LoggerHandler - Bot *tgbotapi.BotAPI - Forwarder forwarderinterface.ForwarderInterface - Migrations migrationsinterface.MigrationsInterface - Router routerinterface.RouterInterface - Parsers parsersinterface.ParsersInterface - Db *sqlx.DB - Talkers talkersinterface.TalkersInterface - Getters gettersinterface.GettersInterface - Welcomer welcomerinterface.WelcomerInterface - Pinner pinnerinterface.PinnerInterface - Chatter chatterinterface.ChatterInterface - Squader squaderinterface.SquaderInterface + Cfg *config.Config + Log *mogrus.LoggerHandler + Bot *tgbotapi.BotAPI + Forwarder forwarderinterface.ForwarderInterface + Migrations migrationsinterface.MigrationsInterface + Router routerinterface.RouterInterface + Pokedexer pokedexerinterface.PokedexerInterface + Db *sqlx.DB + Talkers talkersinterface.TalkersInterface + Broadcaster broadcasterinterface.BroadcasterInterface + Welcomer welcomerinterface.WelcomerInterface + Pinner pinnerinterface.PinnerInterface + Chatter chatterinterface.ChatterInterface + Squader squaderinterface.SquaderInterface + Users usersinterface.UsersInterface + Statistics statisticsinterface.StatisticsInterface } // Init is a initialization function for context @@ -75,9 +79,9 @@ func (c *Context) RegisterMigrationsInterface(mi migrationsinterface.MigrationsI c.Migrations.Init() } -// RegisterParsersInterface registering parsers interface in application -func (c *Context) RegisterParsersInterface(pi parsersinterface.ParsersInterface) { - c.Parsers = pi +// RegisterPokedexerInterface registering parsers interface in application +func (c *Context) RegisterPokedexerInterface(pi pokedexerinterface.PokedexerInterface) { + c.Pokedexer = pi } // RegisterTalkersInterface registering talkers interface in application @@ -86,10 +90,10 @@ func (c *Context) RegisterTalkersInterface(ti talkersinterface.TalkersInterface) c.Talkers.Init() } -// RegisterGettersInterface registering getters interface in application -func (c *Context) RegisterGettersInterface(gi gettersinterface.GettersInterface) { - c.Getters = gi - c.Getters.Init() +// RegisterBroadcasterInterface registering broadcaster interface in application +func (c *Context) RegisterBroadcasterInterface(bi broadcasterinterface.BroadcasterInterface) { + c.Broadcaster = bi + c.Broadcaster.Init() } // RegisterWelcomerInterface registering welcomer interface in application @@ -122,6 +126,18 @@ func (c *Context) RegisterSquaderInterface(si squaderinterface.SquaderInterface) c.Squader.Init() } +// RegisterUsersInterface registers users interface in application +func (c *Context) RegisterUsersInterface(ui usersinterface.UsersInterface) { + c.Users = ui + c.Users.Init() +} + +// RegisterStatisticsInterface registers statistics interface in application +func (c *Context) RegisterStatisticsInterface(si statisticsinterface.StatisticsInterface) { + c.Statistics = si + c.Statistics.Init() +} + // RunDatabaseMigrations applies migrations on bot's startup func (c *Context) RunDatabaseMigrations() { c.Migrations.SetDialect("mysql") diff --git a/lib/getters/broadcasts.go b/lib/broadcaster/broadcaster.go similarity index 74% rename from lib/getters/broadcasts.go rename to lib/broadcaster/broadcaster.go index c5a3e88..ee2894e 100644 --- a/lib/getters/broadcasts.go +++ b/lib/broadcaster/broadcaster.go @@ -1,15 +1,14 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package getters +package broadcaster import ( "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" "time" ) -// CreateBroadcastMessage creates broadcast message item in database -func (g *Getters) CreateBroadcastMessage(playerRaw *dbmapping.Player, messageBody string, broadcastType string) (dbmapping.Broadcast, bool) { +func (b *Broadcaster) createBroadcastMessage(playerRaw *dbmapping.Player, messageBody string, broadcastType string) (dbmapping.Broadcast, bool) { messageRaw := dbmapping.Broadcast{} messageRaw.Text = messageBody messageRaw.Status = "new" @@ -30,8 +29,7 @@ func (g *Getters) CreateBroadcastMessage(playerRaw *dbmapping.Player, messageBod return messageRaw, true } -// GetBroadcastMessageByID returns dbmapping.Broadcast instance with given ID. -func (g *Getters) GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) { +func (b *Broadcaster) getBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) { messageRaw := dbmapping.Broadcast{} err := c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE id=?"), messageID) if err != nil { @@ -42,8 +40,7 @@ func (g *Getters) GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, b return messageRaw, true } -// UpdateBroadcastMessageStatus updates broadcast message status -func (g *Getters) UpdateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) { +func (b *Broadcaster) updateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) { messageRaw := dbmapping.Broadcast{} err := c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE id=?"), messageID) if err != nil { diff --git a/lib/broadcaster/broadcasterinterface/broadcasterinterface.go b/lib/broadcaster/broadcasterinterface/broadcasterinterface.go new file mode 100644 index 0000000..cb01a32 --- /dev/null +++ b/lib/broadcaster/broadcasterinterface/broadcasterinterface.go @@ -0,0 +1,17 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package broadcasterinterface + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +// BroadcasterInterface implements Broadcaster for importing via appcontex +type BroadcasterInterface interface { + Init() + + AdminBroadcastMessageCompose(update *tgbotapi.Update, playerRaw *dbmapping.Player) string + AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw *dbmapping.Player) string +} diff --git a/lib/broadcaster/exported.go b/lib/broadcaster/exported.go new file mode 100644 index 0000000..722b8bb --- /dev/null +++ b/lib/broadcaster/exported.go @@ -0,0 +1,28 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package broadcaster + +import ( + "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/broadcaster/broadcasterinterface" +) + +var ( + c *appcontext.Context +) + +// Broadcaster is a function-handling struct for broadcaster +type Broadcaster struct{} + +// New is a appcontext initialization function +func New(ac *appcontext.Context) { + c = ac + b := &Broadcaster{} + c.RegisterBroadcasterInterface(broadcasterinterface.BroadcasterInterface(b)) +} + +// Init is an initialization function for talkers +func (b *Broadcaster) Init() { + c.Log.Info("Initializing Broadcaster...") +} diff --git a/lib/broadcaster/responders.go b/lib/broadcaster/responders.go new file mode 100644 index 0000000..d452102 --- /dev/null +++ b/lib/broadcaster/responders.go @@ -0,0 +1,47 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package broadcaster + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" + "strconv" + "strings" +) + +// AdminBroadcastMessageCompose saves message for future broadcast +func (b *Broadcaster) AdminBroadcastMessageCompose(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + broadcastingMessageBody := strings.Replace(update.Message.Text, "/send_all ", "", 1) + + messageRaw, ok := b.createBroadcastMessage(playerRaw, broadcastingMessageBody, "all") + if !ok { + return "fail" + } + + message := "Сообщение сохранено в базу.\n" + message += "Выглядеть оно будет так:" + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + broadcastingMessage := "*Привет, %username%!*\n\n" + broadcastingMessage += "*Важное сообщение от администратора " + update.Message.From.FirstName + " " + update.Message.From.LastName + "* (@" + update.Message.From.UserName + ")\n\n" + broadcastingMessage += messageRaw.Text + + msg = tgbotapi.NewMessage(update.Message.Chat.ID, broadcastingMessage) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + message = "Чтобы отправить сообщение всем, отправь команду /send\\_confirm " + strconv.Itoa(messageRaw.ID) + + msg = tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} diff --git a/lib/broadcaster/sender.go b/lib/broadcaster/sender.go new file mode 100644 index 0000000..6962fab --- /dev/null +++ b/lib/broadcaster/sender.go @@ -0,0 +1,59 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package broadcaster + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" + "strconv" + "strings" +) + +// AdminBroadcastMessageSend sends saved message to all private chats +func (b *Broadcaster) AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + messageNum := strings.Replace(update.Message.Text, "/send_confirm ", "", 1) + messageNumInt, _ := strconv.Atoi(messageNum) + messageRaw, ok := b.getBroadcastMessageByID(messageNumInt) + if !ok { + return "fail" + } + if messageRaw.AuthorID != playerRaw.ID { + return "fail" + } + if messageRaw.Status != "new" { + return "fail" + } + + broadcastingMessageBody := messageRaw.Text + + privateChats, ok := c.Chatter.GetAllPrivateChats() + if !ok { + return "fail" + } + + for i := range privateChats { + chat := privateChats[i] + broadcastingMessage := "*Привет, " + chat.Name + "!*\n\n" + broadcastingMessage += "*Важное сообщение от администратора " + update.Message.From.FirstName + " " + update.Message.From.LastName + "* (@" + update.Message.From.UserName + ")\n\n" + broadcastingMessage += broadcastingMessageBody + + msg := tgbotapi.NewMessage(int64(chat.TelegramID), broadcastingMessage) + msg.ParseMode = "Markdown" + c.Bot.Send(msg) + } + + messageRaw, ok = b.updateBroadcastMessageStatus(messageRaw.ID, "sent") + if !ok { + return "fail" + } + + message := "Сообщение всем отправлено. Надеюсь, пользователи бота за него тебя не убьют.\n" + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} diff --git a/lib/config/config.go b/lib/config/config.go index 76e14d1..aa07580 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -10,7 +10,7 @@ import ( "path/filepath" ) -const VERSION = "0.35" +const VERSION = "0.51" // DatabaseConnection handles database connection settings in config.yaml type DatabaseConnection struct { diff --git a/lib/forwarder/exported.go b/lib/forwarder/exported.go index 6241460..2bd8ee6 100644 --- a/lib/forwarder/exported.go +++ b/lib/forwarder/exported.go @@ -24,5 +24,5 @@ func New(ac *appcontext.Context) { // Init is a initialization function for package func (f *Forwarder) Init() { - c.Log.Info("Initializing forwarder...") + c.Log.Info("Initializing Forwarder...") } diff --git a/lib/forwarder/forwarder.go b/lib/forwarder/forwarder.go index 66b80e0..d7ee650 100644 --- a/lib/forwarder/forwarder.go +++ b/lib/forwarder/forwarder.go @@ -20,33 +20,14 @@ func (f *Forwarder) ProcessForward(update *tgbotapi.Update, playerRaw *dbmapping case pokememeMsg.MatchString(text): c.Log.Debug("Pokememe posted!") if playerRaw.LeagueID == 1 { - status := c.Parsers.ParsePokememe(text, playerRaw) - switch status { - case "ok": - c.Talkers.PokememeAddSuccessMessage(update) - return "ok" - case "dup": - c.Talkers.PokememeAddDuplicateMessage(update) - return "ok" - case "fail": - c.Talkers.PokememeAddFailureMessage(update) - return "fail" - } + return c.Pokedexer.ParsePokememe(update, playerRaw) } else { c.Talkers.AnyMessageUnauthorized(update) return "fail" } case profileMsg.MatchString(text): c.Log.Debug("Profile posted!") - status := c.Parsers.ParseProfile(update, playerRaw) - switch status { - case "ok": - c.Talkers.ProfileAddSuccessMessage(update) - return "ok" - case "fail": - c.Talkers.ProfileAddFailureMessage(update) - return "fail" - } + return c.Users.ParseProfile(update, playerRaw) default: c.Log.Debug(text) } diff --git a/lib/getters/exported.go b/lib/getters/exported.go deleted file mode 100644 index cfb4c23..0000000 --- a/lib/getters/exported.go +++ /dev/null @@ -1,28 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package getters - -import ( - "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" - "lab.pztrn.name/fat0troll/i2_bot/lib/getters/gettersinterface" -) - -var ( - c *appcontext.Context -) - -// Getters is a function-handling struct for package getters. -type Getters struct{} - -// New is an initialization function for appcontext -func New(ac *appcontext.Context) { - c = ac - g := &Getters{} - c.RegisterGettersInterface(gettersinterface.GettersInterface(g)) -} - -// Init is a initialization function for package -func (g *Getters) Init() { - c.Log.Info("Initializing getters...") -} diff --git a/lib/getters/gettersinterface/gettersinterface.go b/lib/getters/gettersinterface/gettersinterface.go deleted file mode 100644 index 3736b28..0000000 --- a/lib/getters/gettersinterface/gettersinterface.go +++ /dev/null @@ -1,24 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package gettersinterface - -import ( - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" -) - -// GettersInterface implements Getters for importing via appcontext. -type GettersInterface interface { - Init() - CreateBroadcastMessage(playerRaw *dbmapping.Player, messageBody string, broadcastType string) (dbmapping.Broadcast, bool) - GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) - UpdateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) - GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) - GetPlayerByID(playerID int) (dbmapping.Player, bool) - PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool - GetProfile(playerID int) (dbmapping.Profile, bool) - GetPokememes() ([]dbmapping.PokememeFull, bool) - GetBestPokememes(playerID int) ([]dbmapping.PokememeFull, bool) - GetPokememeByID(pokememeID string) (dbmapping.PokememeFull, bool) - PossibilityRequiredPokeballs(location int, grade int, lvl int) (float64, int) -} diff --git a/lib/getters/profile.go b/lib/getters/profile.go deleted file mode 100644 index 3d21228..0000000 --- a/lib/getters/profile.go +++ /dev/null @@ -1,20 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package getters - -import ( - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" -) - -// GetProfile returns last saved profile of player -func (g *Getters) 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 -} diff --git a/lib/parsers/parsersinterface/parsersinterface.go b/lib/parsers/parsersinterface/parsersinterface.go deleted file mode 100644 index 4e15553..0000000 --- a/lib/parsers/parsersinterface/parsersinterface.go +++ /dev/null @@ -1,18 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package parsersinterface - -import ( - // 3rd party - "github.com/go-telegram-bot-api/telegram-bot-api" - // local - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" -) - -// ParsersInterface implements Parsers for importing via appcontext. -type ParsersInterface interface { - ParsePokememe(text string, playerRaw *dbmapping.Player) string - ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - ReturnPoints(points int) string -} diff --git a/lib/parsers/exported.go b/lib/pokedexer/exported.go similarity index 52% rename from lib/parsers/exported.go rename to lib/pokedexer/exported.go index 19ea140..bb04dc3 100644 --- a/lib/parsers/exported.go +++ b/lib/pokedexer/exported.go @@ -1,24 +1,24 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package parsers +package pokedexer import ( // local "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" - "lab.pztrn.name/fat0troll/i2_bot/lib/parsers/parsersinterface" + "lab.pztrn.name/fat0troll/i2_bot/lib/pokedexer/pokedexerinterface" ) var ( c *appcontext.Context ) -// Parsers is a function-handling struct for package parsers -type Parsers struct{} +// Pokedexer is a function-handling struct for package pokedexer +type Pokedexer struct{} // New is an initialization function for appcontext func New(ac *appcontext.Context) { c = ac - p := &Parsers{} - c.RegisterParsersInterface(parsersinterface.ParsersInterface(p)) + p := &Pokedexer{} + c.RegisterPokedexerInterface(pokedexerinterface.PokedexerInterface(p)) } diff --git a/lib/getters/pokememes.go b/lib/pokedexer/getters.go similarity index 87% rename from lib/getters/pokememes.go rename to lib/pokedexer/getters.go index bd53dee..8353fcf 100644 --- a/lib/getters/pokememes.go +++ b/lib/pokedexer/getters.go @@ -1,7 +1,7 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package getters +package pokedexer import ( "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" @@ -10,7 +10,7 @@ import ( // Internal functions -func (g *Getters) formFullPokememes(pokememes []dbmapping.Pokememe) ([]dbmapping.PokememeFull, bool) { +func (p *Pokedexer) formFullPokememes(pokememes []dbmapping.Pokememe) ([]dbmapping.PokememeFull, bool) { pokememesArray := []dbmapping.PokememeFull{} elements := []dbmapping.Element{} err := c.Db.Select(&elements, "SELECT * FROM elements") @@ -75,7 +75,7 @@ func (g *Getters) formFullPokememes(pokememes []dbmapping.Pokememe) ([]dbmapping // External functions // GetPokememes returns all existing pokememes, known by bot -func (g *Getters) GetPokememes() ([]dbmapping.PokememeFull, bool) { +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") @@ -84,18 +84,17 @@ func (g *Getters) GetPokememes() ([]dbmapping.PokememeFull, bool) { return pokememesArray, false } - pokememesArray, ok := g.formFullPokememes(pokememes) + pokememesArray, ok := p.formFullPokememes(pokememes) return pokememesArray, ok } -// GetBestPokememes returns all pokememes, which will be good for player to catch -func (g *Getters) GetBestPokememes(playerID int) ([]dbmapping.PokememeFull, bool) { +func (p *Pokedexer) getBestPokememes(playerID int) ([]dbmapping.PokememeFull, bool) { pokememesArray := []dbmapping.PokememeFull{} - playerRaw, ok := g.GetPlayerByID(playerID) + playerRaw, ok := c.Users.GetPlayerByID(playerID) if !ok { return pokememesArray, ok } - profileRaw, ok := g.GetProfile(playerID) + profileRaw, ok := c.Users.GetProfile(playerID) if !ok { return pokememesArray, ok } @@ -112,12 +111,12 @@ func (g *Getters) GetBestPokememes(playerID int) ([]dbmapping.PokememeFull, bool return pokememesArray, false } - pokememesArray, ok = g.formFullPokememes(pokememes) + pokememesArray, ok = p.formFullPokememes(pokememes) return pokememesArray, ok } -// GetPokememeByUD returns single pokememe based on internal ID in database -func (g *Getters) GetPokememeByID(pokememeID string) (dbmapping.PokememeFull, bool) { +// 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) diff --git a/lib/parsers/pokememe.go b/lib/pokedexer/parsers.go similarity index 83% rename from lib/parsers/pokememe.go rename to lib/pokedexer/parsers.go index 91b2e8c..fedc0a8 100644 --- a/lib/parsers/pokememe.go +++ b/lib/pokedexer/parsers.go @@ -1,9 +1,10 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package parsers +package pokedexer import ( + "github.com/go-telegram-bot-api/telegram-bot-api" "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" "regexp" "strconv" @@ -11,28 +12,9 @@ import ( "time" ) -// Internal functions - -func (p *Parsers) getPoints(pointsStr string) int { - value := 0 - if strings.HasSuffix(pointsStr, "K") { - valueNumber := strings.Replace(pointsStr, "K", "", 1) - valueFloat, _ := strconv.ParseFloat(valueNumber, 64) - value = int(valueFloat * 1000) - } else if strings.HasSuffix(pointsStr, "M") { - valueNumber := strings.Replace(pointsStr, "M", "", 1) - valueFloat, _ := strconv.ParseFloat(valueNumber, 64) - value = int(valueFloat * 1000000) - } else { - value, _ = strconv.Atoi(pointsStr) - } - return value -} - -// External functions - // ParsePokememe parses pokememe, forwarded from PokememeBroBot, to database -func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string { +func (p *Pokedexer) ParsePokememe(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + text := update.Message.Text var defendablePokememe = false pokememeStringsArray := strings.Split(text, "\n") pokememeRunesArray := make([][]rune, 0) @@ -58,6 +40,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string err := c.Db.Select(&elements, "SELECT * FROM elements WHERE symbol IN ('"+strings.Join(elementEmojis, "', '")+"')") if err != nil { c.Log.Error(err.Error()) + p.pokememeAddFailureMessage(update) return "fail" } @@ -67,6 +50,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string if len(hitPoints) != 3 { c.Log.Error("Can't parse hitpoints!") c.Log.Debug(pokememeRunesArray[5]) + p.pokememeAddFailureMessage(update) return "fail" } @@ -84,6 +68,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string if len(defenceMatch) < 1 { c.Log.Error("Can't parse defence!") c.Log.Debug(pokememeRunesArray[6]) + p.pokememeAddFailureMessage(update) return "fail" } defence = defenceMatch[0] @@ -91,6 +76,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string if len(priceMatch) < 1 { c.Log.Error("Can't parse price!") c.Log.Debug(pokememeRunesArray[7]) + p.pokememeAddFailureMessage(update) return "fail" } price = priceMatch[0] @@ -98,18 +84,21 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string 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]), "Можно") { @@ -123,6 +112,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string if len(priceMatch) < 1 { c.Log.Error("Can't parse price!") c.Log.Debug(pokememeRunesArray[6]) + p.pokememeAddFailureMessage(update) return "fail" } price = priceMatch[0] @@ -130,18 +120,21 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string 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]), "Можно") { @@ -184,15 +177,16 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string 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 := p.getPoints(hitPoints[0]) - hpInt := p.getPoints(hitPoints[1]) - mpInt := p.getPoints(hitPoints[2]) - defenceInt := p.getPoints(defence) - priceInt := p.getPoints(price) + 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 @@ -214,6 +208,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string _, 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()) + p.pokememeAddFailureMessage(update) return "fail" } @@ -221,6 +216,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string 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!") + p.pokememeAddFailureMessage(update) return "fail" } for i := range elements { @@ -232,6 +228,7 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string _, 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" } } @@ -244,23 +241,11 @@ func (p *Parsers) ParsePokememe(text string, playerRaw *dbmapping.Player) string _, 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) return "ok" } - -// ReturnPoints returns to output points (ht, attack, mp...) formatted -// like in PokememBroBot itself. -func (p *Parsers) ReturnPoints(points int) string { - if points < 1000 { - return strconv.Itoa(points) - } else if points < 1000000 { - floatNum := float64(points) / 1000.0 - return strconv.FormatFloat(floatNum, 'f', -1, 64) + "K" - } else { - floatNum := float64(points) / 1000000.0 - return strconv.FormatFloat(floatNum, 'f', -1, 64) + "M" - } -} diff --git a/lib/pokedexer/pokedexer.go b/lib/pokedexer/pokedexer.go new file mode 100644 index 0000000..1175412 --- /dev/null +++ b/lib/pokedexer/pokedexer.go @@ -0,0 +1,85 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package pokedexer + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" + "strconv" +) + +func (p *Pokedexer) pokememesListing(update *tgbotapi.Update, page int, pokememesArray []dbmapping.PokememeFull) { + message := "*Известные боту покемемы*\n" + message += "Список отсортирован по грейду и алфавиту.\n" + message += "Покедекс: " + strconv.Itoa(len(pokememesArray)) + " / 219\n" + message += "Отображаем покемемов с " + strconv.Itoa(((page-1)*50)+1) + " по " + strconv.Itoa(page*50) + "\n" + if len(pokememesArray) > page*50 { + message += "Переход на следующую страницу: /pokedeks" + strconv.Itoa(page+1) + } + if page > 1 { + message += "\nПереход на предыдущую страницу: /pokedeks" + strconv.Itoa(page-1) + } + message += "\n\n" + + for i := range pokememesArray { + if (i+1 > 50*(page-1)) && (i+1 < (50*page)+1) { + pk := pokememesArray[i].Pokememe + pkE := pokememesArray[i].Elements + message += strconv.Itoa(i+1) + ". " + strconv.Itoa(pk.Grade) + message += "⃣ *" + pk.Name + message += "* (" + c.Statistics.GetPrintablePoints(pk.HP) + "-" + c.Statistics.GetPrintablePoints(pk.MP) + ") ⚔️ *" + message += c.Statistics.GetPrintablePoints(pk.Attack) + "* \\[" + for j := range pkE { + message += pkE[j].Symbol + } + message += "] " + c.Statistics.GetPrintablePoints(pk.Price) + "$ /pk" + strconv.Itoa(pk.ID) + message += "\n" + } + } + + if len(pokememesArray) > page*50 { + message += "\n" + message += "Переход на следующую страницу: /pokedeks" + strconv.Itoa(page+1) + } + if page > 1 { + message += "\nПереход на предыдущую страницу: /pokedeks" + strconv.Itoa(page-1) + } + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) +} + +func (p *Pokedexer) pokememeAddSuccessMessage(update *tgbotapi.Update) { + message := "*Покемем успешно добавлен.*\n\n" + message += "Посмотреть всех известных боту покемемов можно командой /pokedeks" + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) +} + +func (p *Pokedexer) pokememeAddDuplicateMessage(update *tgbotapi.Update) { + message := "*Мы уже знаем об этом покемеме*\n\n" + message += "Посмотреть всех известных боту покемемов можно командой /pokedeks\n\n" + message += "Если у покемема изменились описание или характеристики, напиши @fat0troll для обновления базы." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) +} + +func (p *Pokedexer) pokememeAddFailureMessage(update *tgbotapi.Update) { + message := "*Неудачно получилось :(*\n\n" + message += "Случилась жуткая ошибка, и мы не смогли записать покемема в базу. Напиши @fat0troll, он разберется.\n\n" + message += "Посмотреть всех известных боту покемемов можно командой /pokedeks" + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) +} diff --git a/lib/pokedexer/pokedexerinterface/pokedexerinterface.go b/lib/pokedexer/pokedexerinterface/pokedexerinterface.go new file mode 100644 index 0000000..e82ec4a --- /dev/null +++ b/lib/pokedexer/pokedexerinterface/pokedexerinterface.go @@ -0,0 +1,21 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package pokedexerinterface + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +// PokedexerInterface implements Pokedexer for importing via appcontext. +type PokedexerInterface interface { + ParsePokememe(update *tgbotapi.Update, playerRaw *dbmapping.Player) string + + PokememesList(update *tgbotapi.Update) + 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) +} diff --git a/lib/pokedexer/responders.go b/lib/pokedexer/responders.go new file mode 100644 index 0000000..6fe6ff6 --- /dev/null +++ b/lib/pokedexer/responders.go @@ -0,0 +1,148 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package pokedexer + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" + "strconv" + "strings" +) + +// BestPokememesList shows list for catching based on player league and grade +func (p *Pokedexer) BestPokememesList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + pokememes, ok := p.getBestPokememes(playerRaw.ID) + if !ok { + c.Log.Error("Cannot get pokememes from getter!") + return "fail" + } + + message := "*Лучшие покемемы для ловли*\n\n" + for i := range pokememes { + pk := pokememes[i].Pokememe + pkL := pokememes[i].Locations + pkE := pokememes[i].Elements + message += strconv.Itoa(pk.Grade) + "⃣ " + message += pk.Name + " (⚔" + message += c.Statistics.GetPrintablePoints(pk.Attack) + message += ", 🛡" + c.Statistics.GetPrintablePoints(pk.Defence) + ")" + for i := range pkE { + message += pkE[i].Symbol + } + message += " /pk" + strconv.Itoa(pk.ID) + "\n" + message += "Локации: " + for i := range pkL { + message += pkL[i].Symbol + pkL[i].Name + if i+1 < len(pkL) { + message += ", " + } + } + message += "\nКупить: " + if pk.Purchaseable { + message += "💲" + c.Statistics.GetPrintablePoints(pk.Price*3) + } else { + message += "Нельзя" + } + message += "\n\n" + } + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} + +// PokememesList lists all known pokememes +func (p *Pokedexer) PokememesList(update *tgbotapi.Update) { + pageNumber := strings.Replace(update.Message.Text, "/pokedex", "", 1) + pageNumber = strings.Replace(pageNumber, "/pokedeks", "", 1) + page, _ := strconv.Atoi(pageNumber) + if page == 0 { + page = 1 + } + pokememesArray, ok := p.GetPokememes() + if !ok { + c.Talkers.BotError(update) + } else { + 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 { + calculatePossibilites = false + } + + pokememe, ok := p.GetPokememeByID(pokememeNumber) + if !ok { + return "fail" + } + + pk := pokememe.Pokememe + + message := strconv.Itoa(pk.Grade) + "⃣ *" + pk.Name + "*\n" + message += pk.Description + "\n\n" + message += "Элементы:" + for i := range pokememe.Elements { + message += " " + pokememe.Elements[i].Symbol + } + message += "\n⚔ Атака: *" + c.Statistics.GetPrintablePoints(pk.Attack) + message += "*\n❤️ HP: *" + c.Statistics.GetPrintablePoints(pk.HP) + message += "*\n💙 MP: *" + c.Statistics.GetPrintablePoints(pk.MP) + if pk.Defence != pk.Attack { + message += "*\n🛡Защита: *" + c.Statistics.GetPrintablePoints(pk.Defence) + "* _(сопротивляемость покемема к поимке)_" + } else { + message += "*" + } + message += "\nСтоимость: *" + c.Statistics.GetPrintablePoints(pk.Price) + message += "*\nКупить: *" + if pk.Purchaseable { + message += "Можно" + } else { + message += "Нельзя" + } + message += "*\nОбитает:" + for i := range pokememe.Locations { + message += " *" + pokememe.Locations[i].Name + "*" + if (i + 1) < len(pokememe.Locations) { + message += "," + } + } + + if calculatePossibilites { + if (pk.Grade < profileRaw.LevelID+2) && (pk.Grade > profileRaw.LevelID-3) { + message += "\nВероятность поимки:" + for i := range pokememe.Locations { + percentile, pokeballs := c.Statistics.PossibilityRequiredPokeballs(pokememe.Locations[i].ID, pk.Grade, profileRaw.LevelID) + message += "\n" + pokememe.Locations[i].Name + " – " + message += strconv.FormatFloat(percentile, 'f', 2, 64) + "% или " + message += strconv.Itoa(pokeballs) + "⭕" + } + } + } + + message += "\n" + pk.ImageURL + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + keyboard := tgbotapi.InlineKeyboardMarkup{} + for i := range pokememe.Locations { + var row []tgbotapi.InlineKeyboardButton + btn := tgbotapi.NewInlineKeyboardButtonSwitch(pokememe.Locations[i].Symbol+pokememe.Locations[i].Name, pokememe.Locations[i].Symbol+pokememe.Locations[i].Name) + row = append(row, btn) + keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, row) + } + + msg.ReplyMarkup = keyboard + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} diff --git a/lib/router/group_request.go b/lib/router/group_request.go index 7fba87e..7ca1bfa 100644 --- a/lib/router/group_request.go +++ b/lib/router/group_request.go @@ -22,7 +22,7 @@ func (r *Router) routeGroupRequest(update *tgbotapi.Update, playerRaw *dbmapping if update.Message.NewChatMembers != nil { newUsers := *update.Message.NewChatMembers if len(newUsers) > 0 { - return c.Welcomer.WelcomeMessage(update) + return c.Welcomer.GroupWelcomeMessage(update) } } // New chat names diff --git a/lib/router/private_request.go b/lib/router/private_request.go index c837edd..47c3937 100644 --- a/lib/router/private_request.go +++ b/lib/router/private_request.go @@ -33,51 +33,51 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi switch { case update.Message.Command() == "start": if playerRaw.ID != 0 { - c.Talkers.HelloMessageAuthorized(update, playerRaw) + c.Welcomer.PrivateWelcomeMessageAuthorized(update, playerRaw) return "ok" } - c.Talkers.HelloMessageUnauthorized(update) + c.Welcomer.PrivateWelcomeMessageUnauthorized(update) return "ok" case update.Message.Command() == "help": c.Talkers.HelpMessage(update, playerRaw) return "ok" // Pokememes info case pokedexMsg.MatchString(text): - c.Talkers.PokememesList(update) + c.Pokedexer.PokememesList(update) return "ok" case pokememeInfoMsg.MatchString(text): - c.Talkers.PokememeInfo(update, playerRaw) + c.Pokedexer.PokememeInfo(update, playerRaw) return "ok" case update.Message.Command() == "me": if playerRaw.ID != 0 { - c.Talkers.ProfileMessage(update, playerRaw) + c.Users.ProfileMessage(update, playerRaw) return "ok" } c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "best": - c.Talkers.BestPokememesList(update, playerRaw) + c.Pokedexer.BestPokememesList(update, playerRaw) return "ok" case update.Message.Command() == "send_all": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { - c.Talkers.AdminBroadcastMessageCompose(update, playerRaw) + if c.Users.PlayerBetterThan(playerRaw, "admin") { + c.Broadcaster.AdminBroadcastMessageCompose(update, playerRaw) return "ok" } c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "send_confirm": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { - c.Talkers.AdminBroadcastMessageSend(update, playerRaw) + if c.Users.PlayerBetterThan(playerRaw, "admin") { + c.Broadcaster.AdminBroadcastMessageSend(update, playerRaw) return "ok" } c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "group_chats": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { c.Chatter.GroupsList(update) return "ok" } @@ -85,7 +85,7 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "squads": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { c.Squader.SquadsList(update) return "ok" } @@ -93,14 +93,14 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "make_squad": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { return c.Squader.CreateSquad(update) } c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "pin": - if c.Getters.PlayerBetterThan(playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { return c.Pinner.PinMessageToAllChats(update) } diff --git a/lib/router/router.go b/lib/router/router.go index 621e20c..67bb8b3 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -9,7 +9,7 @@ import ( // RouteRequest decides, what to do with user input func (r *Router) RouteRequest(update *tgbotapi.Update) string { - playerRaw, ok := c.Getters.GetOrCreatePlayer(update.Message.From.ID) + playerRaw, ok := c.Users.GetOrCreatePlayer(update.Message.From.ID) if !ok { // Silently fail return "fail" diff --git a/lib/squader/squader.go b/lib/squader/squader.go index 777eec8..b7b8522 100644 --- a/lib/squader/squader.go +++ b/lib/squader/squader.go @@ -74,7 +74,7 @@ func (s *Squader) createSquad(update *tgbotapi.Update, chatID int, floodChatID i if err != nil { c.Log.Debug(err) - playerRaw, ok := c.Getters.GetOrCreatePlayer(update.Message.From.ID) + playerRaw, ok := c.Users.GetOrCreatePlayer(update.Message.From.ID) if !ok { return squad, "fail" } @@ -240,8 +240,8 @@ func (s *Squader) SquadStatictics(squadID int) string { for i := range squadMembers { fullInfo := dbmapping.SquadPlayerFull{} - playerRaw, _ := c.Getters.GetPlayerByID(squadMembers[i].PlayerID) - profileRaw, _ := c.Getters.GetProfile(playerRaw.ID) + playerRaw, _ := c.Users.GetPlayerByID(squadMembers[i].PlayerID) + profileRaw, _ := c.Users.GetProfile(playerRaw.ID) fullInfo.Squad = squad fullInfo.Player = playerRaw diff --git a/lib/statistics/exported.go b/lib/statistics/exported.go new file mode 100644 index 0000000..f269d4c --- /dev/null +++ b/lib/statistics/exported.go @@ -0,0 +1,28 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package statistics + +import ( + "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/statistics/statisticsinterface" +) + +var ( + c *appcontext.Context +) + +// Statistics is a function-handling struct for package statistics. +type Statistics struct{} + +// New is an initialization function for appcontext +func New(ac *appcontext.Context) { + c = ac + s := &Statistics{} + c.RegisterStatisticsInterface(statisticsinterface.StatisticsInterface(s)) +} + +// Init is a initialization function for package +func (s *Statistics) Init() { + c.Log.Info("Initializing Statistics...") +} diff --git a/lib/statistics/points.go b/lib/statistics/points.go new file mode 100644 index 0000000..69687e9 --- /dev/null +++ b/lib/statistics/points.go @@ -0,0 +1,40 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package statistics + +import ( + "strconv" + "strings" +) + +// GetPoints returns points to use in database +func (s *Statistics) GetPoints(pointsStr string) int { + value := 0 + if strings.HasSuffix(pointsStr, "K") { + valueNumber := strings.Replace(pointsStr, "K", "", 1) + valueFloat, _ := strconv.ParseFloat(valueNumber, 64) + value = int(valueFloat * 1000) + } else if strings.HasSuffix(pointsStr, "M") { + valueNumber := strings.Replace(pointsStr, "M", "", 1) + valueFloat, _ := strconv.ParseFloat(valueNumber, 64) + value = int(valueFloat * 1000000) + } else { + value, _ = strconv.Atoi(pointsStr) + } + return value +} + +// GetPrintablePoints returns to output points (ht, attack, mp...) formatted +// like in PokememBroBot itself. +func (s *Statistics) GetPrintablePoints(points int) string { + if points < 1000 { + return strconv.Itoa(points) + } else if points < 1000000 { + floatNum := float64(points) / 1000.0 + return strconv.FormatFloat(floatNum, 'f', -1, 64) + "K" + } else { + floatNum := float64(points) / 1000000.0 + return strconv.FormatFloat(floatNum, 'f', -1, 64) + "M" + } +} diff --git a/lib/getters/possibility.go b/lib/statistics/possibilities.go similarity index 93% rename from lib/getters/possibility.go rename to lib/statistics/possibilities.go index dca9339..84996c6 100644 --- a/lib/getters/possibility.go +++ b/lib/statistics/possibilities.go @@ -1,11 +1,11 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package getters +package statistics // PossibilityRequiredPokeballs returns possibility of catching pokememe // It's based on location, grade of pokememe and current level of player -func (g *Getters) PossibilityRequiredPokeballs(location int, grade int, lvl int) (float64, int) { +func (s *Statistics) PossibilityRequiredPokeballs(location int, grade int, lvl int) (float64, int) { var basePossibility float64 var requiredPokeballs int var percentile float64 diff --git a/lib/statistics/statisticsinterface/statisticinterface.go b/lib/statistics/statisticsinterface/statisticinterface.go new file mode 100644 index 0000000..5e5db3f --- /dev/null +++ b/lib/statistics/statisticsinterface/statisticinterface.go @@ -0,0 +1,14 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package statisticsinterface + +// StatisticsInterface implements Statistics for importing via appcontext. +type StatisticsInterface interface { + Init() + + GetPoints(pointsStr string) int + GetPrintablePoints(points int) string + + PossibilityRequiredPokeballs(location int, grade int, lvl int) (float64, int) +} diff --git a/lib/talkers/broadcast.go b/lib/talkers/broadcast.go deleted file mode 100644 index 7fd63ea..0000000 --- a/lib/talkers/broadcast.go +++ /dev/null @@ -1,95 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package talkers - -import ( - "github.com/go-telegram-bot-api/telegram-bot-api" - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" - "strconv" - "strings" -) - -// AdminBroadcastMessageCompose saves message for future broadcast -func (t *Talkers) AdminBroadcastMessageCompose(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - broadcastingMessageBody := strings.Replace(update.Message.Text, "/send_all ", "", 1) - - messageRaw, ok := c.Getters.CreateBroadcastMessage(playerRaw, broadcastingMessageBody, "all") - if !ok { - return "fail" - } - - message := "Сообщение сохранено в базу.\n" - message += "Выглядеть оно будет так:" - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - broadcastingMessage := "*Привет, %username%!*\n\n" - broadcastingMessage += "*Важное сообщение от администратора " + update.Message.From.FirstName + " " + update.Message.From.LastName + "* (@" + update.Message.From.UserName + ")\n\n" - broadcastingMessage += messageRaw.Text - - msg = tgbotapi.NewMessage(update.Message.Chat.ID, broadcastingMessage) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - message = "Чтобы отправить сообщение всем, отправь команду /send\\_confirm " + strconv.Itoa(messageRaw.ID) - - msg = tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - return "ok" -} - -// AdminBroadcastMessageSend sends saved message to all private chats -func (t *Talkers) AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - messageNum := strings.Replace(update.Message.Text, "/send_confirm ", "", 1) - messageNumInt, _ := strconv.Atoi(messageNum) - messageRaw, ok := c.Getters.GetBroadcastMessageByID(messageNumInt) - if !ok { - return "fail" - } - if messageRaw.AuthorID != playerRaw.ID { - return "fail" - } - if messageRaw.Status != "new" { - return "fail" - } - - broadcastingMessageBody := messageRaw.Text - - privateChats, ok := c.Chatter.GetAllPrivateChats() - if !ok { - return "fail" - } - - for i := range privateChats { - chat := privateChats[i] - broadcastingMessage := "*Привет, " + chat.Name + "!*\n\n" - broadcastingMessage += "*Важное сообщение от администратора " + update.Message.From.FirstName + " " + update.Message.From.LastName + "* (@" + update.Message.From.UserName + ")\n\n" - broadcastingMessage += broadcastingMessageBody - - msg := tgbotapi.NewMessage(int64(chat.TelegramID), broadcastingMessage) - msg.ParseMode = "Markdown" - c.Bot.Send(msg) - } - - messageRaw, ok = c.Getters.UpdateBroadcastMessageStatus(messageRaw.ID, "sent") - if !ok { - return "fail" - } - - message := "Сообщение всем отправлено. Надеюсь, пользователи бота за него тебя не убьют.\n" - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - return "ok" -} diff --git a/lib/talkers/errors.go b/lib/talkers/errors.go index 4f03cad..7799e1d 100644 --- a/lib/talkers/errors.go +++ b/lib/talkers/errors.go @@ -18,8 +18,8 @@ func (t *Talkers) AnyMessageUnauthorized(update *tgbotapi.Update) { c.Bot.Send(msg) } -// GetterError throws when bot can't get something -func (t *Talkers) GetterError(update *tgbotapi.Update) { +// BotError throws when bot can't do something +func (t *Talkers) BotError(update *tgbotapi.Update) { message := "Ой, внутренняя ошибка в боте :(\n\n" message += "Напиши @fat0troll, приложив форвардом последние сообщения до этого.\n" diff --git a/lib/talkers/exported.go b/lib/talkers/exported.go index 19e10c4..c5672d1 100644 --- a/lib/talkers/exported.go +++ b/lib/talkers/exported.go @@ -24,5 +24,5 @@ func New(ac *appcontext.Context) { // Init is an initialization function for talkers func (t *Talkers) Init() { - c.Log.Info("Initializing responders...") + c.Log.Info("Initializing common Responders...") } diff --git a/lib/talkers/help.go b/lib/talkers/help.go index fc35656..4e93a4d 100644 --- a/lib/talkers/help.go +++ b/lib/talkers/help.go @@ -17,7 +17,7 @@ func (t *Talkers) HelpMessage(update *tgbotapi.Update, playerRaw *dbmapping.Play message += "+ /me – посмотреть свой сохраненный профиль в боте\n" message += "+ /best – посмотреть лучших покемонов для поимки\n" message += "+ /pokedeks – получить список известных боту покемемов\n" - if c.Getters.PlayerBetterThan(playerRaw, "admin") { + if c.Users.PlayerBetterThan(playerRaw, "admin") { message += "+ /send\\_all _текст_ — отправить сообщение всем пользователям бота\n" message += "+ /group\\_chats — получить список групп, в которых работает бот.\n" message += "+ /squads — получить список отрядов.\n" diff --git a/lib/talkers/pokedex.go b/lib/talkers/pokedex.go deleted file mode 100644 index 20402e1..0000000 --- a/lib/talkers/pokedex.go +++ /dev/null @@ -1,151 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package talkers - -import ( - "github.com/go-telegram-bot-api/telegram-bot-api" - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" - "strconv" - "strings" -) - -// Internal functions - -func (t *Talkers) pokememesListing(update *tgbotapi.Update, page int, pokememesArray []dbmapping.PokememeFull) { - message := "*Известные боту покемемы*\n" - message += "Список отсортирован по грейду и алфавиту.\n" - message += "Покедекс: " + strconv.Itoa(len(pokememesArray)) + " / 219\n" - message += "Отображаем покемемов с " + strconv.Itoa(((page-1)*50)+1) + " по " + strconv.Itoa(page*50) + "\n" - if len(pokememesArray) > page*50 { - message += "Переход на следующую страницу: /pokedeks" + strconv.Itoa(page+1) - } - if page > 1 { - message += "\nПереход на предыдущую страницу: /pokedeks" + strconv.Itoa(page-1) - } - message += "\n\n" - - for i := range pokememesArray { - if (i+1 > 50*(page-1)) && (i+1 < (50*page)+1) { - pk := pokememesArray[i].Pokememe - pkE := pokememesArray[i].Elements - message += strconv.Itoa(i+1) + ". " + strconv.Itoa(pk.Grade) - message += "⃣ *" + pk.Name - message += "* (" + c.Parsers.ReturnPoints(pk.HP) + "-" + c.Parsers.ReturnPoints(pk.MP) + ") ⚔️ *" - message += c.Parsers.ReturnPoints(pk.Attack) + "* \\[" - for j := range pkE { - message += pkE[j].Symbol - } - message += "] " + c.Parsers.ReturnPoints(pk.Price) + "$ /pk" + strconv.Itoa(pk.ID) - message += "\n" - } - } - - if len(pokememesArray) > page*50 { - message += "\n" - message += "Переход на следующую страницу: /pokedeks" + strconv.Itoa(page+1) - } - if page > 1 { - message += "\nПереход на предыдущую страницу: /pokedeks" + strconv.Itoa(page-1) - } - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - -} - -// External functions - -// PokememesList lists all known pokememes -func (t *Talkers) PokememesList(update *tgbotapi.Update) { - pageNumber := strings.Replace(update.Message.Text, "/pokedex", "", 1) - pageNumber = strings.Replace(pageNumber, "/pokedeks", "", 1) - page, _ := strconv.Atoi(pageNumber) - if page == 0 { - page = 1 - } - pokememesArray, ok := c.Getters.GetPokememes() - if !ok { - t.GetterError(update) - } else { - t.pokememesListing(update, page, pokememesArray) - } -} - -// PokememeInfo shows information about single pokememe based on internal ID -func (t *Talkers) PokememeInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - pokememeNumber := strings.Replace(update.Message.Text, "/pk", "", 1) - var calculatePossibilites = true - profileRaw, ok := c.Getters.GetProfile(playerRaw.ID) - if !ok { - calculatePossibilites = false - } - - pokememe, ok := c.Getters.GetPokememeByID(pokememeNumber) - if !ok { - return "fail" - } - - pk := pokememe.Pokememe - - message := strconv.Itoa(pk.Grade) + "⃣ *" + pk.Name + "*\n" - message += pk.Description + "\n\n" - message += "Элементы:" - for i := range pokememe.Elements { - message += " " + pokememe.Elements[i].Symbol - } - message += "\n⚔ Атака: *" + c.Parsers.ReturnPoints(pk.Attack) - message += "*\n❤️ HP: *" + c.Parsers.ReturnPoints(pk.HP) - message += "*\n💙 MP: *" + c.Parsers.ReturnPoints(pk.MP) - if pk.Defence != pk.Attack { - message += "*\n🛡Защита: *" + c.Parsers.ReturnPoints(pk.Defence) + "* _(сопротивляемость покемема к поимке)_" - } else { - message += "*" - } - message += "\nСтоимость: *" + c.Parsers.ReturnPoints(pk.Price) - message += "*\nКупить: *" - if pk.Purchaseable { - message += "Можно" - } else { - message += "Нельзя" - } - message += "*\nОбитает:" - for i := range pokememe.Locations { - message += " *" + pokememe.Locations[i].Name + "*" - if (i + 1) < len(pokememe.Locations) { - message += "," - } - } - - if calculatePossibilites { - if (pk.Grade < profileRaw.LevelID+2) && (pk.Grade > profileRaw.LevelID-3) { - message += "\nВероятность поимки:" - for i := range pokememe.Locations { - percentile, pokeballs := c.Getters.PossibilityRequiredPokeballs(pokememe.Locations[i].ID, pk.Grade, profileRaw.LevelID) - message += "\n" + pokememe.Locations[i].Name + " – " - message += strconv.FormatFloat(percentile, 'f', 2, 64) + "% или " - message += strconv.Itoa(pokeballs) + "⭕" - } - } - } - - message += "\n" + pk.ImageURL - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - keyboard := tgbotapi.InlineKeyboardMarkup{} - for i := range pokememe.Locations { - var row []tgbotapi.InlineKeyboardButton - btn := tgbotapi.NewInlineKeyboardButtonSwitch(pokememe.Locations[i].Symbol+pokememe.Locations[i].Name, pokememe.Locations[i].Symbol+pokememe.Locations[i].Name) - row = append(row, btn) - keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, row) - } - - msg.ReplyMarkup = keyboard - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - return "ok" -} diff --git a/lib/talkers/pokememe_add.go b/lib/talkers/pokememe_add.go deleted file mode 100644 index e06383d..0000000 --- a/lib/talkers/pokememe_add.go +++ /dev/null @@ -1,43 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package talkers - -import ( - "github.com/go-telegram-bot-api/telegram-bot-api" -) - -// PokememeAddSuccessMessage shows pokememe adding success message -func (t *Talkers) PokememeAddSuccessMessage(update *tgbotapi.Update) { - message := "*Покемем успешно добавлен.*\n\n" - message += "Посмотреть всех известных боту покемемов можно командой /pokedeks" - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) -} - -// PokememeAddDuplicateMessage shows pokememe add duplication message -func (t *Talkers) PokememeAddDuplicateMessage(update *tgbotapi.Update) { - message := "*Мы уже знаем об этом покемеме*\n\n" - message += "Посмотреть всех известных боту покемемов можно командой /pokedeks\n\n" - message += "Если у покемема изменились описание или характеристики, напиши @fat0troll для обновления базы." - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) -} - -// PokememeAddFailureMessage shows pokememe add error message -func (t *Talkers) PokememeAddFailureMessage(update *tgbotapi.Update) { - message := "*Неудачно получилось :(*\n\n" - message += "Случилась жуткая ошибка, и мы не смогли записать покемема в базу. Напиши @fat0troll, он разберется.\n\n" - message += "Посмотреть всех известных боту покемемов можно командой /pokedeks" - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) -} diff --git a/lib/talkers/suggestions.go b/lib/talkers/suggestions.go deleted file mode 100644 index 2b5cc3b..0000000 --- a/lib/talkers/suggestions.go +++ /dev/null @@ -1,55 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package talkers - -import ( - "github.com/go-telegram-bot-api/telegram-bot-api" - "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" - "strconv" -) - -// BestPokememesList shows list for catching based on player league and grade -func (t *Talkers) BestPokememesList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - pokememes, ok := c.Getters.GetBestPokememes(playerRaw.ID) - if !ok { - c.Log.Error("Cannot get pokememes from getter!") - return "fail" - } - - message := "*Лучшие покемемы для ловли*\n\n" - for i := range pokememes { - pk := pokememes[i].Pokememe - pkL := pokememes[i].Locations - pkE := pokememes[i].Elements - message += strconv.Itoa(pk.Grade) + "⃣ " - message += pk.Name + " (⚔" - message += c.Parsers.ReturnPoints(pk.Attack) - message += ", 🛡" + c.Parsers.ReturnPoints(pk.Defence) + ")" - for i := range pkE { - message += pkE[i].Symbol - } - message += " /pk" + strconv.Itoa(pk.ID) + "\n" - message += "Локации: " - for i := range pkL { - message += pkL[i].Symbol + pkL[i].Name - if i+1 < len(pkL) { - message += ", " - } - } - message += "\nКупить: " - if pk.Purchaseable { - message += "💲" + c.Parsers.ReturnPoints(pk.Price*3) - } else { - message += "Нельзя" - } - message += "\n\n" - } - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) - msg.ParseMode = "Markdown" - - c.Bot.Send(msg) - - return "ok" -} diff --git a/lib/talkers/talkersinterface/talkersinterface.go b/lib/talkers/talkersinterface/talkersinterface.go index 844db2c..f795d11 100644 --- a/lib/talkers/talkersinterface/talkersinterface.go +++ b/lib/talkers/talkersinterface/talkersinterface.go @@ -11,25 +11,10 @@ import ( // TalkersInterface implements Talkers for importing via appcontex type TalkersInterface interface { Init() - HelloMessageUnauthorized(update *tgbotapi.Update) - HelloMessageAuthorized(update *tgbotapi.Update, playerRaw *dbmapping.Player) HelpMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) - PokememesList(update *tgbotapi.Update) - PokememeInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - BestPokememesList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - - PokememeAddSuccessMessage(update *tgbotapi.Update) - PokememeAddDuplicateMessage(update *tgbotapi.Update) - PokememeAddFailureMessage(update *tgbotapi.Update) - ProfileAddSuccessMessage(update *tgbotapi.Update) - ProfileAddFailureMessage(update *tgbotapi.Update) - ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string AnyMessageUnauthorized(update *tgbotapi.Update) - GetterError(update *tgbotapi.Update) - - AdminBroadcastMessageCompose(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw *dbmapping.Player) string + BotError(update *tgbotapi.Update) DurakMessage(update *tgbotapi.Update) MatMessage(update *tgbotapi.Update) diff --git a/lib/users/exported.go b/lib/users/exported.go new file mode 100644 index 0000000..c02bdfa --- /dev/null +++ b/lib/users/exported.go @@ -0,0 +1,28 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package users + +import ( + "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/users/usersinterface" +) + +var ( + c *appcontext.Context +) + +// Users is a function-handling struct for users +type Users struct{} + +// New is a appcontext initialization function +func New(ac *appcontext.Context) { + c = ac + u := &Users{} + c.RegisterUsersInterface(usersinterface.UsersInterface(u)) +} + +// Init is an initialization function for users +func (u *Users) Init() { + c.Log.Info("Initializing Users...") +} diff --git a/lib/getters/player.go b/lib/users/getters.go similarity index 72% rename from lib/getters/player.go rename to lib/users/getters.go index c98f9ae..2defd16 100644 --- a/lib/getters/player.go +++ b/lib/users/getters.go @@ -1,15 +1,27 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package getters +package users import ( "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" "time" ) +// 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 (g *Getters) GetPlayerByID(playerID int) (dbmapping.Player, bool) { +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 { @@ -22,7 +34,7 @@ func (g *Getters) GetPlayerByID(playerID int) (dbmapping.Player, bool) { // 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 (g *Getters) GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) { +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=?"), telegramID) if err != nil { @@ -49,7 +61,7 @@ func (g *Getters) GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) { // PlayerBetterThan return true, if profile is more or equal powerful than // provided power level -func (g *Getters) PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool { +func (u *Users) PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool { var isBetter = false switch playerRaw.Status { case "owner": diff --git a/lib/parsers/profile.go b/lib/users/parsers.go similarity index 89% rename from lib/parsers/profile.go rename to lib/users/parsers.go index a8a147d..aef2417 100644 --- a/lib/parsers/profile.go +++ b/lib/users/parsers.go @@ -1,7 +1,7 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package parsers +package users import ( "github.com/go-telegram-bot-api/telegram-bot-api" @@ -14,13 +14,13 @@ import ( // Internal functions -func (p *Parsers) fillProfilePokememe(profileID int, meme string, attack string, rarity string) { +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+"';")) if err != nil { c.Log.Error(err.Error()) } else { - attackInt := p.getPoints(attack) + attackInt := c.Statistics.GetPoints(attack) ppk := dbmapping.ProfilePokememe{} ppk.ProfileID = profileID ppk.PokememeID = spkRaw.ID @@ -37,7 +37,7 @@ func (p *Parsers) fillProfilePokememe(profileID int, meme string, attack string, // External functions // ParseProfile parses user profile, forwarded from PokememBroBot, to database -func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { +func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { text := update.Message.Text c.Log.Info(text) @@ -78,6 +78,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla err1 := c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE symbol='"+string(currentRunes[0])+"'")) if err1 != nil { c.Log.Error(err1.Error()) + u.profileAddFailureMessage(update) return "fail" } for j := range currentRunes { @@ -91,6 +92,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla levelArray := levelRx.FindAllString(currentString, -1) if len(levelArray) < 1 { c.Log.Error("Level string broken") + u.profileAddFailureMessage(update) return "fail" } level = levelArray[0] @@ -102,6 +104,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla expArray := expRx.FindAllString(currentString, -1) if len(expArray) < 4 { c.Log.Error("Exp string broken") + u.profileAddFailureMessage(update) return "fail" } exp = expArray[0] @@ -115,6 +118,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla pkbArray := pkbRx.FindAllString(currentString, -1) if len(pkbArray) < 2 { c.Log.Error("Pokeballs string broken") + u.profileAddFailureMessage(update) return "fail" } pokeballs = pkbArray[1] @@ -126,12 +130,13 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla wealthArray := wealthRx.FindAllString(currentString, -1) if len(wealthArray) < 2 { c.Log.Error("Wealth string broken") + u.profileAddFailureMessage(update) return "fail" } wealth = wealthArray[0] - wealthInt = p.getPoints(wealth) + wealthInt = c.Statistics.GetPoints(wealth) crystalls = wealthArray[1] - crystallsInt = p.getPoints(crystalls) + crystallsInt = c.Statistics.GetPoints(crystalls) } if strings.HasPrefix(currentString, "🔫") { @@ -146,11 +151,12 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla pkNumArray := pkmnumRx.FindAllString(currentString, -1) if len(pkNumArray) < 3 { c.Log.Error("Pokememes count broken") + u.profileAddFailureMessage(update) return "fail" } pokememesCount, _ := strconv.Atoi(pkNumArray[0]) pokememesWealth = pkNumArray[2] - pokememesWealthInt = p.getPoints(pokememesWealth) + pokememesWealthInt = c.Statistics.GetPoints(pokememesWealth) if pokememesCount > 0 { for pi := 0; pi < pokememesCount; pi++ { pokememeString := string(profileRunesArray[i+1+pi]) @@ -162,7 +168,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla pkName = strings.TrimSuffix(pkName, " ") pkName = strings.Split(pkName, "⃣ ")[1] pokememes[pkName] = pkAttack - powerInt += p.getPoints(pkAttack) + powerInt += c.Statistics.GetPoints(pkAttack) } } } @@ -210,6 +216,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla _, 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()) + u.profileAddFailureMessage(update) return "fail" } } else if playerRaw.LeagueID != league.ID { @@ -220,11 +227,13 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla _, 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()) + u.profileAddFailureMessage(update) return "fail" } } @@ -247,6 +256,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla _, 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()) + u.profileAddFailureMessage(update) return "fail" } @@ -254,6 +264,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla if err8 != nil { c.Log.Error(err8.Error()) c.Log.Error("Profile isn't added!") + u.profileAddFailureMessage(update) return "fail" } @@ -261,6 +272,7 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla _, err7 := c.Db.NamedExec("UPDATE `players` SET updated_at=:updated_at WHERE id=:id", &playerRaw) if err7 != nil { c.Log.Error(err7.Error()) + u.profileAddFailureMessage(update) return "fail" } @@ -282,8 +294,9 @@ func (p *Parsers) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Pla rarity = "super liber" meme = strings.Replace(meme, "🔷", "", 1) } - p.fillProfilePokememe(profileRaw.ID, meme, attack, rarity) + u.fillProfilePokememe(profileRaw.ID, meme, attack, rarity) } + u.profileAddSuccessMessage(update) return "ok" } diff --git a/lib/talkers/profile.go b/lib/users/responders.go similarity index 81% rename from lib/talkers/profile.go rename to lib/users/responders.go index e796a21..307fdad 100644 --- a/lib/talkers/profile.go +++ b/lib/users/responders.go @@ -1,7 +1,7 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package talkers +package users import ( "github.com/go-telegram-bot-api/telegram-bot-api" @@ -10,8 +10,8 @@ import ( ) // ProfileMessage shows current player's profile -func (t *Talkers) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { - profileRaw, ok := c.Getters.GetProfile(playerRaw.ID) +func (u *Users) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + profileRaw, ok := u.GetProfile(playerRaw.ID) if !ok { c.Talkers.AnyMessageUnauthorized(update) return "fail" @@ -60,13 +60,13 @@ func (t *Talkers) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.P message += "\n👤 " + strconv.Itoa(profileRaw.LevelID) message += " | 🎓 " + strconv.Itoa(profileRaw.Exp) + "/" + strconv.Itoa(level.MaxExp) message += " | 🥚 " + strconv.Itoa(profileRaw.EggExp) + "/" + strconv.Itoa(level.MaxEgg) - message += "\n💲" + c.Parsers.ReturnPoints(profileRaw.Wealth) + message += "\n💲" + c.Statistics.GetPrintablePoints(profileRaw.Wealth) message += " |💎" + strconv.Itoa(profileRaw.Crystalls) message += " |⭕" + strconv.Itoa(profileRaw.Pokeballs) - message += "\n⚔Атака: 1 + " + c.Parsers.ReturnPoints(weapon.Power) + " + " + c.Parsers.ReturnPoints(attackPokememes) + "\n" + message += "\n⚔Атака: 1 + " + c.Statistics.GetPrintablePoints(weapon.Power) + " + " + c.Statistics.GetPrintablePoints(attackPokememes) + "\n" if profileRaw.WeaponID != 0 { - message += "\n🔫Оружие: " + weapon.Name + " " + c.Parsers.ReturnPoints(weapon.Power) + "⚔" + message += "\n🔫Оружие: " + weapon.Name + " " + c.Statistics.GetPrintablePoints(weapon.Power) + "⚔" } message += "\n🐱Покемемы:" @@ -75,11 +75,11 @@ func (t *Talkers) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.P if profilePokememes[i].PokememeID == pokememes[j].ID { message += "\n" + strconv.Itoa(pokememes[j].Grade) message += "⃣ " + pokememes[j].Name - message += " +" + c.Parsers.ReturnPoints(profilePokememes[i].PokememeAttack) + "⚔" + message += " +" + c.Statistics.GetPrintablePoints(profilePokememes[i].PokememeAttack) + "⚔" } } } - message += "\nСтоимость покемемов на руках: " + c.Parsers.ReturnPoints(profileRaw.PokememesWealth) + "$" + message += "\nСтоимость покемемов на руках: " + c.Statistics.GetPrintablePoints(profileRaw.PokememesWealth) + "$" message += "\n\n💳" + strconv.Itoa(playerRaw.TelegramID) message += "\n⏰Последнее обновление профиля: " + profileRaw.CreatedAt.Format("02.01.2006 15:04:05") message += "\n\nНе забывай обновляться, это важно для получения актуальной информации.\n\n" diff --git a/lib/talkers/profile_add.go b/lib/users/users.go similarity index 77% rename from lib/talkers/profile_add.go rename to lib/users/users.go index 9d8e315..59940e4 100644 --- a/lib/talkers/profile_add.go +++ b/lib/users/users.go @@ -1,14 +1,16 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package talkers +package users import ( "github.com/go-telegram-bot-api/telegram-bot-api" ) -// ProfileAddSuccessMessage shows profile addition success message -func (t *Talkers) ProfileAddSuccessMessage(update *tgbotapi.Update) { +// Internal functions for Users package + +// profileAddSuccessMessage shows profile addition success message +func (u *Users) profileAddSuccessMessage(update *tgbotapi.Update) { message := "*Профиль успешно обновлен.*\n\n" message += "Функциональность бота держится на актуальности профилей. Обновляйся почаще, и да пребудет с тобой Рандом!\n" message += "Сохраненный профиль ты можешь просмотреть командой /me.\n\n" @@ -20,8 +22,8 @@ func (t *Talkers) ProfileAddSuccessMessage(update *tgbotapi.Update) { c.Bot.Send(msg) } -// ProfileAddFailureMessage shows profile addition failure message -func (t *Talkers) ProfileAddFailureMessage(update *tgbotapi.Update) { +// profileAddFailureMessage shows profile addition failure message +func (u *Users) profileAddFailureMessage(update *tgbotapi.Update) { message := "*Неудачно получилось :(*\n\n" message += "Случилась жуткая ошибка, и мы не смогли записать профиль в базу. Напиши @fat0troll, он разберется." diff --git a/lib/users/usersinterface/usersinterface.go b/lib/users/usersinterface/usersinterface.go new file mode 100644 index 0000000..3d62669 --- /dev/null +++ b/lib/users/usersinterface/usersinterface.go @@ -0,0 +1,23 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package usersinterface + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +// UsersInterface implements Users for importing via appcontex +type UsersInterface interface { + Init() + + ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) 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 + + ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string +} diff --git a/lib/talkers/hello.go b/lib/welcomer/responders.go similarity index 62% rename from lib/talkers/hello.go rename to lib/welcomer/responders.go index 1a8f8a4..0f67341 100644 --- a/lib/talkers/hello.go +++ b/lib/welcomer/responders.go @@ -1,15 +1,15 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package talkers +package welcomer import ( "github.com/go-telegram-bot-api/telegram-bot-api" "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" ) -// HelloMessageUnauthorized tell new user what to do. -func (t *Talkers) HelloMessageUnauthorized(update *tgbotapi.Update) { +// PrivateWelcomeMessageUnauthorized tell new user what to do. +func (w *Welcomer) PrivateWelcomeMessageUnauthorized(update *tgbotapi.Update) { message := "*Бот Инстинкта приветствует тебя!*\n\n" message += "Для начала работы с ботом, пожалуйста, перешли от бота игры @PokememBroBot профиль героя.\n" message += "Все дальнейшие действия с ботом возможны лишь при наличии профиля игрока." @@ -20,8 +20,8 @@ func (t *Talkers) HelloMessageUnauthorized(update *tgbotapi.Update) { c.Bot.Send(msg) } -// HelloMessageAuthorized greets existing user -func (t *Talkers) HelloMessageAuthorized(update *tgbotapi.Update, playerRaw *dbmapping.Player) { +// PrivateWelcomeMessageAuthorized greets existing user +func (w *Welcomer) PrivateWelcomeMessageAuthorized(update *tgbotapi.Update, playerRaw *dbmapping.Player) { message := "*Бот Инстинкта приветствует тебя. Снова.*\n\n" message += "Привет, " + update.Message.From.FirstName + " " + update.Message.From.LastName + "!\n" message += "Последнее обновление информации о тебе: " + playerRaw.UpdatedAt.Format("02.01.2006 15:04:05 -0700") @@ -31,3 +31,18 @@ func (t *Talkers) HelloMessageAuthorized(update *tgbotapi.Update, playerRaw *dbm c.Bot.Send(msg) } + +// GroupWelcomeMessage welcomes new user on group or bot itself +func (w *Welcomer) GroupWelcomeMessage(update *tgbotapi.Update) string { + newUsers := *update.Message.NewChatMembers + for i := range newUsers { + if (newUsers[i].UserName == "i2_bot") || (newUsers[i].UserName == "i2_dev_bot") { + w.groupStartMessage(update) + } + + newUser := newUsers[i] + w.groupWelcomeUser(update, &newUser) + } + + return "ok" +} diff --git a/lib/welcomer/welcomer.go b/lib/welcomer/welcomer.go index 40a9061..9481ec3 100644 --- a/lib/welcomer/welcomer.go +++ b/lib/welcomer/welcomer.go @@ -9,12 +9,12 @@ import ( ) func (w *Welcomer) groupWelcomeUser(update *tgbotapi.Update, newUser *tgbotapi.User) string { - playerRaw, ok := c.Getters.GetOrCreatePlayer(newUser.ID) + playerRaw, ok := c.Users.GetOrCreatePlayer(newUser.ID) if !ok { return "fail" } - profileRaw, profileExist := c.Getters.GetProfile(playerRaw.ID) + profileRaw, profileExist := c.Users.GetProfile(playerRaw.ID) message := "*Бот Инстинкта приветствует тебя, *@" message += newUser.UserName @@ -54,19 +54,4 @@ func (w *Welcomer) groupStartMessage(update *tgbotapi.Update) string { c.Bot.Send(msg) return "ok" -} - -// WelcomeMessage welcomes new user on group or bot itself -func (w *Welcomer) WelcomeMessage(update *tgbotapi.Update) string { - newUsers := *update.Message.NewChatMembers - for i := range newUsers { - if (newUsers[i].UserName == "i2_bot") || (newUsers[i].UserName == "i2_dev_bot") { - w.groupStartMessage(update) - } - - newUser := newUsers[i] - w.groupWelcomeUser(update, &newUser) - } - - return "ok" -} +} \ No newline at end of file diff --git a/lib/welcomer/welcomerinterface/welcomerinterface.go b/lib/welcomer/welcomerinterface/welcomerinterface.go index d6d99ae..9d60e75 100644 --- a/lib/welcomer/welcomerinterface/welcomerinterface.go +++ b/lib/welcomer/welcomerinterface/welcomerinterface.go @@ -5,10 +5,14 @@ package welcomerinterface import ( "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" ) // WelcomerInterface implements Welcomer for importing via appcontex type WelcomerInterface interface { Init() - WelcomeMessage(update *tgbotapi.Update) string + + PrivateWelcomeMessageUnauthorized(update *tgbotapi.Update) + PrivateWelcomeMessageAuthorized(update *tgbotapi.Update, playerRaw *dbmapping.Player) + GroupWelcomeMessage(update *tgbotapi.Update) string }