diff --git a/cmd/i2_bot/i2_bot.go b/cmd/i2_bot/i2_bot.go index 25a3320..19fb4e3 100644 --- a/cmd/i2_bot/i2_bot.go +++ b/cmd/i2_bot/i2_bot.go @@ -20,9 +20,6 @@ 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" - "net/http" - "time" ) var ( @@ -57,32 +54,5 @@ func main() { c.Cron.Start() c.Log.Info("> Cron started.") - _, err := c.Bot.SetWebhook(tgbotapi.NewWebhook(c.Cfg.Telegram.WebHookDomain + c.Bot.Token)) - if err != nil { - c.Log.Fatal(err.Error()) - } - - updates := c.Bot.ListenForWebhook("/" + c.Bot.Token) - go http.ListenAndServe(c.Cfg.Telegram.ListenAddress, nil) - - c.Log.Info("Listening on " + c.Cfg.Telegram.ListenAddress) - c.Log.Info("Webhook URL: " + c.Cfg.Telegram.WebHookDomain + c.Bot.Token) - - for update := range updates { - if update.Message != nil { - if update.Message.From != nil { - if update.Message.Date > (int(time.Now().Unix()) - 5) { - go c.Router.RouteRequest(&update) - } - } - } else if update.InlineQuery != nil { - c.Router.RouteInline(&update) - } else if update.CallbackQuery != nil { - c.Router.RouteCallback(&update) - } else if update.ChosenInlineResult != nil { - c.Log.Debug(update.ChosenInlineResult.ResultID) - } else { - continue - } - } + c.StartBot() } diff --git a/lib/appcontext/appcontext.go b/lib/appcontext/appcontext.go index dc648fd..d4b66c9 100644 --- a/lib/appcontext/appcontext.go +++ b/lib/appcontext/appcontext.go @@ -26,7 +26,9 @@ import ( "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/jmoiron/sqlx" "github.com/robfig/cron" + "net/http" "os" + "time" ) // Context is an application context struct @@ -100,59 +102,12 @@ func (c *Context) Init() { c.Cron = crontab } -// RegisterRouterInterface registering router interface in application -func (c *Context) RegisterRouterInterface(ri routerinterface.RouterInterface) { - c.Router = ri - c.Router.Init() -} - -// RegisterMigrationsInterface registering migrations interface in application -func (c *Context) RegisterMigrationsInterface(mi migrationsinterface.MigrationsInterface) { - c.Migrations = mi - c.Migrations.Init() -} - -// RegisterPokedexerInterface registering parsers interface in application -func (c *Context) RegisterPokedexerInterface(pi pokedexerinterface.PokedexerInterface) { - c.Pokedexer = pi -} - -// RegisterTalkersInterface registering talkers interface in application -func (c *Context) RegisterTalkersInterface(ti talkersinterface.TalkersInterface) { - c.Talkers = ti - c.Talkers.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 -func (c *Context) RegisterWelcomerInterface(wi welcomerinterface.WelcomerInterface) { - c.Welcomer = wi - c.Welcomer.Init() -} - -// RegisterPinnerInterface registering pinner interface in application -func (c *Context) RegisterPinnerInterface(pi pinnerinterface.PinnerInterface) { - c.Pinner = pi - c.Pinner.Init() -} - -// RegisterReminderInterface registering reminder interface in application -func (c *Context) RegisterReminderInterface(ri reminderinterface.ReminderInterface) { - c.Reminder = ri - c.Reminder.Init() -} - -// RegisterForwarderInterface registers forwarder interface in application -func (c *Context) RegisterForwarderInterface(fi forwarderinterface.ForwarderInterface) { - c.Forwarder = fi - c.Forwarder.Init() -} - // RegisterChatterInterface registers chatter interface in application func (c *Context) RegisterChatterInterface(ci chatterinterface.ChatterInterface) { c.Chatter = ci @@ -165,10 +120,16 @@ func (c *Context) RegisterDataCacheInterface(di datacacheinterface.DataCacheInte c.DataCache.Init() } -// RegisterSquaderInterface registers squader interface in application -func (c *Context) RegisterSquaderInterface(si squaderinterface.SquaderInterface) { - c.Squader = si - c.Squader.Init() +// RegisterForwarderInterface registers forwarder interface in application +func (c *Context) RegisterForwarderInterface(fi forwarderinterface.ForwarderInterface) { + c.Forwarder = fi + c.Forwarder.Init() +} + +// RegisterMigrationsInterface registering migrations interface in application +func (c *Context) RegisterMigrationsInterface(mi migrationsinterface.MigrationsInterface) { + c.Migrations = mi + c.Migrations.Init() } // RegisterOrdersInterface registers orders interface in application @@ -177,10 +138,27 @@ func (c *Context) RegisterOrdersInterface(oi ordersinterface.OrdersInterface) { c.Orders.Init() } -// RegisterUsersInterface registers users interface in application -func (c *Context) RegisterUsersInterface(ui usersinterface.UsersInterface) { - c.Users = ui - c.Users.Init() +// RegisterPinnerInterface registering pinner interface in application +func (c *Context) RegisterPinnerInterface(pi pinnerinterface.PinnerInterface) { + c.Pinner = pi + c.Pinner.Init() +} + +// RegisterPokedexerInterface registering parsers interface in application +func (c *Context) RegisterPokedexerInterface(pi pokedexerinterface.PokedexerInterface) { + c.Pokedexer = pi +} + +// RegisterReminderInterface registering reminder interface in application +func (c *Context) RegisterReminderInterface(ri reminderinterface.ReminderInterface) { + c.Reminder = ri + c.Reminder.Init() +} + +// RegisterRouterInterface registering router interface in application +func (c *Context) RegisterRouterInterface(ri routerinterface.RouterInterface) { + c.Router = ri + c.Router.Init() } // RegisterStatisticsInterface registers statistics interface in application @@ -189,8 +167,64 @@ func (c *Context) RegisterStatisticsInterface(si statisticsinterface.StatisticsI c.Statistics.Init() } +// RegisterSquaderInterface registers squader interface in application +func (c *Context) RegisterSquaderInterface(si squaderinterface.SquaderInterface) { + c.Squader = si + c.Squader.Init() +} + +// RegisterTalkersInterface registering talkers interface in application +func (c *Context) RegisterTalkersInterface(ti talkersinterface.TalkersInterface) { + c.Talkers = ti + c.Talkers.Init() +} + +// RegisterWelcomerInterface registering welcomer interface in application +func (c *Context) RegisterWelcomerInterface(wi welcomerinterface.WelcomerInterface) { + c.Welcomer = wi + c.Welcomer.Init() +} + +// RegisterUsersInterface registers users interface in application +func (c *Context) RegisterUsersInterface(ui usersinterface.UsersInterface) { + c.Users = ui + c.Users.Init() +} + // RunDatabaseMigrations applies migrations on bot's startup func (c *Context) RunDatabaseMigrations() { c.Migrations.SetDialect("mysql") c.Migrations.Migrate() } + +// StartBot starts listening for Telegram updates +func (c *Context) StartBot() { + _, err := c.Bot.SetWebhook(tgbotapi.NewWebhook(c.Cfg.Telegram.WebHookDomain + c.Bot.Token)) + if err != nil { + c.Log.Fatal(err.Error()) + } + + updates := c.Bot.ListenForWebhook("/" + c.Bot.Token) + go http.ListenAndServe(c.Cfg.Telegram.ListenAddress, nil) + + c.Log.Info("Listening on " + c.Cfg.Telegram.ListenAddress) + c.Log.Info("Webhook URL: " + c.Cfg.Telegram.WebHookDomain + c.Bot.Token) + + for update := range updates { + if update.Message != nil { + if update.Message.From != nil { + if update.Message.Date > (int(time.Now().Unix()) - 5) { + go c.Router.RouteRequest(&update) + } + } + } else if update.InlineQuery != nil { + c.Router.RouteInline(&update) + } else if update.CallbackQuery != nil { + c.Router.RouteCallback(&update) + } else if update.ChosenInlineResult != nil { + c.Log.Debug(update.ChosenInlineResult.ResultID) + } else { + continue + } + } +} diff --git a/lib/dbmapping/profiles.go b/lib/dbmapping/profiles.go index 9ce1cc7..24b2d03 100644 --- a/lib/dbmapping/profiles.go +++ b/lib/dbmapping/profiles.go @@ -4,6 +4,7 @@ package dbmapping import ( + "math" "time" ) @@ -24,3 +25,15 @@ type Profile struct { Crystalls int `db:"crystalls"` CreatedAt time.Time `db:"created_at"` } + +// FullExp returns exp points in summary, gained after registration +func (p *Profile) FullExp() int { + fullExp := 0.00 + + for i := 1; i < p.LevelID+1; i++ { + fullExp = fullExp + (100 * math.Pow(2.0, float64(i))) + } + + fullExp += float64(p.Exp) + return int(fullExp) +} diff --git a/lib/router/private_request.go b/lib/router/private_request.go index 45c82bb..af130a8 100644 --- a/lib/router/private_request.go +++ b/lib/router/private_request.go @@ -75,8 +75,19 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi c.Users.ProfileMessage(update, playerRaw) return "ok" } + case update.Message.Command() == "top": + if playerRaw.ID != 0 { + return c.Statistics.TopList(update, playerRaw) + } return c.Talkers.AnyMessageUnauthorized(update) + case update.Message.Command() == "top_my": + if playerRaw.ID != 0 { + return c.Statistics.TopList(update, playerRaw) + } + + return c.Talkers.AnyMessageUnauthorized(update) + case update.Message.Command() == "best": c.Pokedexer.BestPokememesList(update, playerRaw) return "ok" diff --git a/lib/statistics/statisticsinterface/statisticinterface.go b/lib/statistics/statisticsinterface/statisticinterface.go index b7a7258..3c5e93b 100644 --- a/lib/statistics/statisticsinterface/statisticinterface.go +++ b/lib/statistics/statisticsinterface/statisticinterface.go @@ -3,6 +3,11 @@ package statisticsinterface +import ( + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" +) + // StatisticsInterface implements Statistics for importing via appcontext. type StatisticsInterface interface { Init() @@ -13,4 +18,6 @@ type StatisticsInterface interface { GetPrintablePoints(points int) string PossibilityRequiredPokeballs(location int, grade int, lvl int) (float64, int) + + TopList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string } diff --git a/lib/statistics/top.go b/lib/statistics/top.go new file mode 100644 index 0000000..eb8da74 --- /dev/null +++ b/lib/statistics/top.go @@ -0,0 +1,130 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package statistics + +import ( + "git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping" + "github.com/go-telegram-bot-api/telegram-bot-api" + "sort" + "strconv" +) + +func (s *Statistics) renderPosition(profilesRaw *[]*dbmapping.PlayerProfile, playerRaw *dbmapping.Player) string { + render := "_…а ты в этом топе на " + profiles := *profilesRaw + c.Log.Debugln(len(profiles)) + for i := range profiles { + if profiles[i].Player.ID == playerRaw.ID { + render += strconv.Itoa(i + 1) + // Russian numericals... + if ((i+1)%10) == 3 && ((i+1)%100 == 13) { + render += "-ем" + } else { + render += "-ом" + } + render += " месте_\n" + } + } + + return render +} + +// TopList returns list of top users by level, money ans so on +func (s *Statistics) TopList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string { + allPlayers := c.DataCache.GetPlayersWithCurrentProfiles() + myProfile, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) + return c.Talkers.AnyMessageUnauthorized(update) + } + + profiles := make([]*dbmapping.PlayerProfile, 0) + + for i := range allPlayers { + if allPlayers[i].Player.LeagueID == 1 { + if update.Message.Command() == "top" { + profiles = append(profiles, allPlayers[i]) + } else { + // Local top of level + if allPlayers[i].Profile.LevelID == myProfile.LevelID { + profiles = append(profiles, allPlayers[i]) + } + } + } + } + + topLimit := 5 + if len(profiles) < 5 { + topLimit = len(profiles) + } + + message := "*Топ-5 по атаке (без биты)*\n" + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Profile.Power > profiles[j].Profile.Power + }) + + for i := 0; i < topLimit; i++ { + message += "*" + strconv.Itoa(i+1) + "*: " + c.Users.FormatUsername(profiles[i].Profile.Nickname) + " (⚔️" + s.GetPrintablePoints(profiles[i].Profile.Power) + ")\n" + } + + message += s.renderPosition(&profiles, playerRaw) + + message += "\n*Топ-5 по богатству*\n" + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Profile.Wealth > profiles[j].Profile.Wealth + }) + + for i := 0; i < topLimit; i++ { + message += "*" + strconv.Itoa(i+1) + "*: " + c.Users.FormatUsername(profiles[i].Profile.Nickname) + " (💲" + s.GetPrintablePoints(profiles[i].Profile.Wealth) + ")\n" + } + + message += s.renderPosition(&profiles, playerRaw) + + message += "\n*Топ-5 по стоимости покемемов в руке*\n" + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Profile.PokememesWealth > profiles[j].Profile.PokememesWealth + }) + + for i := 0; i < topLimit; i++ { + message += "*" + strconv.Itoa(i+1) + "*: " + c.Users.FormatUsername(profiles[i].Profile.Nickname) + " (💲" + s.GetPrintablePoints(profiles[i].Profile.PokememesWealth) + ")\n" + } + + message += s.renderPosition(&profiles, playerRaw) + + message += "\n*Топ-5 по лимиту покеболов*\n" + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Profile.Pokeballs > profiles[j].Profile.Pokeballs + }) + + for i := 0; i < topLimit; i++ { + message += "*" + strconv.Itoa(i+1) + "*: " + c.Users.FormatUsername(profiles[i].Profile.Nickname) + " (⭕️" + s.GetPrintablePoints(profiles[i].Profile.Pokeballs) + ")\n" + } + + message += s.renderPosition(&profiles, playerRaw) + + message += "\n*Топ-5 по опыту*\n" + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i].Profile.FullExp() > profiles[j].Profile.FullExp() + }) + + for i := 0; i < topLimit; i++ { + message += "*" + strconv.Itoa(i+1) + "*: " + c.Users.FormatUsername(profiles[i].Profile.Nickname) + " (" + strconv.Itoa(profiles[i].Profile.FullExp()) + " очков)\n" + } + + message += s.renderPosition(&profiles, playerRaw) + + message += "\nИгроков, принявших участие в статистике: " + strconv.Itoa(len(profiles)) + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} diff --git a/lib/talkers/help.go b/lib/talkers/help.go index b2033bf..decdb66 100644 --- a/lib/talkers/help.go +++ b/lib/talkers/help.go @@ -54,6 +54,8 @@ func (t *Talkers) HelpMessage(update *tgbotapi.Update, playerRaw *dbmapping.Play message += "Список команд\n\n" message += "\\* /me – посмотреть свой сохраненный профиль в боте\n" message += "\\* /best – посмотреть лучших покемонов для поимки\n" + message += "\\* /top — топ игроков лиги\n" + message += "\\* /top\\_my — топ игроков лиги твоего уровня\n" message += "\\* /pokedeks – получить список известных боту покемемов\n" message += "\\* /reminders — настроить оповещения на Турнир лиг\n" message += "\\* /academy — Академия Инстинкта\n" diff --git a/lib/users/responders.go b/lib/users/responders.go index e6bb4d7..48d71d1 100644 --- a/lib/users/responders.go +++ b/lib/users/responders.go @@ -187,7 +187,9 @@ func (u *Users) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Pla message += "\n\n⏰Последнее обновление профиля: " + profileRaw.CreatedAt.Format("02.01.2006 15:04:05") message += "\nНе забывай обновляться, это важно для получения актуальной информации.\n\n" - message += "/best – посмотреть лучших покемемов для поимки" + message += "/best – посмотреть лучших покемемов для поимки\n" + message += "/top — посмотреть лучших игроков лиги\n" + message += "/top\\_my — посмотреть лучших игроков лиги твоего уровня\n" c.Log.Debug(message) diff --git a/lib/users/users.go b/lib/users/users.go index cdaa591..0804edc 100644 --- a/lib/users/users.go +++ b/lib/users/users.go @@ -91,7 +91,9 @@ func (u *Users) profileAddSuccessMessage(update *tgbotapi.Update, leagueID int, message := "*Профиль успешно обновлен.*\n\n" message += "Функциональность бота держится на актуальности профилей. Обновляйся почаще, и да пребудет с тобой Рандом!\n" message += "Сохраненный профиль ты можешь просмотреть командой /me.\n\n" - message += "/best – посмотреть лучших покемемов для поимки" + message += "/best – посмотреть лучших покемемов для поимки\n" + message += "/top — посмотреть лучших представителей лиги\n" + message += "/top\\_my — посмотреть лучших представителей лиги твоего уровня\n" if leagueID == 1 { message += "\n/bastion — получить ссылку на БАСТИОН лиги\n"