From e4102e9a90d17c91f22cf47d764b72838fff115b Mon Sep 17 00:00:00 2001 From: Vladimir Hodakov Date: Sun, 19 Nov 2017 22:16:11 +0400 Subject: [PATCH] Work on squads, some refactoring Closes #14 See #8 --- README.md | 2 +- cmd/i2_bot/i2_bot.go | 4 + lib/appcontext/appcontext.go | 16 ++ .../chatterinterface/chatterinterface.go | 24 ++ lib/chatter/exported.go | 28 ++ lib/{getters/chat.go => chatter/getters.go} | 90 +++--- lib/chatter/responders.go | 42 +++ lib/chatter/updaters.go | 43 +++ lib/dbmapping/chats.go | 7 + lib/dbmapping/squads.go | 15 +- lib/dbmapping/squads_players.go | 7 + .../gettersinterface/gettersinterface.go | 7 - lib/migrations/22_add_flood_chat_id.go | 29 ++ lib/migrations/migrations.go | 1 + lib/pinner/pinner.go | 2 +- lib/router/group_request.go | 12 +- lib/router/private_request.go | 17 +- lib/router/router.go | 2 +- lib/squader/exported.go | 28 ++ lib/squader/squader.go | 257 ++++++++++++++++++ .../squaderinterface/squaderinterface.go | 16 ++ lib/talkers/broadcast.go | 6 +- lib/talkers/chats.go | 37 --- lib/talkers/help.go | 1 + .../talkersinterface/talkersinterface.go | 2 - lib/welcomer/notifications.go | 4 +- 26 files changed, 587 insertions(+), 112 deletions(-) create mode 100644 lib/chatter/chatterinterface/chatterinterface.go create mode 100644 lib/chatter/exported.go rename lib/{getters/chat.go => chatter/getters.go} (74%) create mode 100644 lib/chatter/responders.go create mode 100644 lib/chatter/updaters.go create mode 100644 lib/migrations/22_add_flood_chat_id.go create mode 100644 lib/squader/exported.go create mode 100644 lib/squader/squader.go create mode 100644 lib/squader/squaderinterface/squaderinterface.go delete mode 100644 lib/talkers/chats.go diff --git a/README.md b/README.md index b5cc7de..b082074 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # i2_bot: бот лиги Инстинкт игры @PokememBroBot -Для запуска нужен правильный ``config.json``. +Для запуска нужен правильный ``config.yml``. Управление зависимостями осуществляет [dep](https://github.com/golang/dep). diff --git a/cmd/i2_bot/i2_bot.go b/cmd/i2_bot/i2_bot.go index 8ed4871..cc16b9e 100644 --- a/cmd/i2_bot/i2_bot.go +++ b/cmd/i2_bot/i2_bot.go @@ -6,12 +6,14 @@ 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/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/router" + "lab.pztrn.name/fat0troll/i2_bot/lib/squader" "lab.pztrn.name/fat0troll/i2_bot/lib/talkers" "lab.pztrn.name/fat0troll/i2_bot/lib/welcomer" "time" @@ -33,6 +35,8 @@ func main() { talkers.New(c) getters.New(c) welcomer.New(c) + chatter.New(c) + squader.New(c) c.Log.Info("=======================") c.Log.Info("= i2_bot initialized. =") diff --git a/lib/appcontext/appcontext.go b/lib/appcontext/appcontext.go index bc22f18..a89cf0c 100644 --- a/lib/appcontext/appcontext.go +++ b/lib/appcontext/appcontext.go @@ -6,6 +6,7 @@ package appcontext import ( "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/jmoiron/sqlx" + "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" @@ -14,6 +15,7 @@ import ( "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/router/routerinterface" + "lab.pztrn.name/fat0troll/i2_bot/lib/squader/squaderinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/talkers/talkersinterface" "lab.pztrn.name/fat0troll/i2_bot/lib/welcomer/welcomerinterface" "lab.pztrn.name/golibs/mogrus" @@ -34,6 +36,8 @@ type Context struct { Getters gettersinterface.GettersInterface Welcomer welcomerinterface.WelcomerInterface Pinner pinnerinterface.PinnerInterface + Chatter chatterinterface.ChatterInterface + Squader squaderinterface.SquaderInterface } // Init is a initialization function for context @@ -106,6 +110,18 @@ func (c *Context) RegisterForwarderInterface(fi forwarderinterface.ForwarderInte c.Forwarder.Init() } +// RegisterChatterInterface registers chatter interface in application +func (c *Context) RegisterChatterInterface(ci chatterinterface.ChatterInterface) { + c.Chatter = ci + c.Chatter.Init() +} + +// RegisterSquaderInterface registers squader interface in application +func (c *Context) RegisterSquaderInterface(si squaderinterface.SquaderInterface) { + c.Squader = si + c.Squader.Init() +} + // RunDatabaseMigrations applies migrations on bot's startup func (c *Context) RunDatabaseMigrations() { c.Migrations.SetDialect("mysql") diff --git a/lib/chatter/chatterinterface/chatterinterface.go b/lib/chatter/chatterinterface/chatterinterface.go new file mode 100644 index 0000000..a0cb109 --- /dev/null +++ b/lib/chatter/chatterinterface/chatterinterface.go @@ -0,0 +1,24 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package chatterinterface + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +// ChatterInterface implements Chatter for importing via appcontext. +type ChatterInterface interface { + Init() + + GetOrCreateChat(update *tgbotapi.Update) (dbmapping.Chat, bool) + GetChatByID(chatID int64) (dbmapping.Chat, bool) + GetAllPrivateChats() ([]dbmapping.Chat, bool) + GetAllGroupChats() ([]dbmapping.Chat, bool) + + UpdateChatTitle(chatRaw *dbmapping.Chat, newTitle string) (*dbmapping.Chat, bool) + UpdateChatTelegramID(update *tgbotapi.Update) (*dbmapping.Chat, bool) + + GroupsList(update *tgbotapi.Update) string +} diff --git a/lib/chatter/exported.go b/lib/chatter/exported.go new file mode 100644 index 0000000..7d67145 --- /dev/null +++ b/lib/chatter/exported.go @@ -0,0 +1,28 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package chatter + +import ( + "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/chatter/chatterinterface" +) + +var ( + c *appcontext.Context +) + +// Chatter is a function-handling struct for package chatter. +type Chatter struct{} + +// New is an initialization function for appcontext +func New(ac *appcontext.Context) { + c = ac + ct := &Chatter{} + c.RegisterChatterInterface(chatterinterface.ChatterInterface(ct)) +} + +// Init is a initialization function for package +func (ct *Chatter) Init() { + c.Log.Info("Initializing Chatter...") +} diff --git a/lib/getters/chat.go b/lib/chatter/getters.go similarity index 74% rename from lib/getters/chat.go rename to lib/chatter/getters.go index ff6507d..204bcdd 100644 --- a/lib/getters/chat.go +++ b/lib/chatter/getters.go @@ -1,7 +1,7 @@ // i2_bot – Instinct PokememBro Bot // Copyright (c) 2017 Vladimir "fat0troll" Hodakov -package getters +package chatter import ( "github.com/go-telegram-bot-api/telegram-bot-api" @@ -9,8 +9,43 @@ import ( "time" ) +func (ct *Chatter) getAllGroupChatsWithSquads() ([]dbmapping.ChatSquad, bool) { + chatsSquads := []dbmapping.ChatSquad{} + groupChats := []dbmapping.Chat{} + + err := c.Db.Select(&groupChats, "SELECT * FROM chats WHERE chat_type IN ('group', 'supergroup')") + if err != nil { + c.Log.Error(err) + return chatsSquads, false + } + + for i := range groupChats { + chatSquad := dbmapping.ChatSquad{} + squad := dbmapping.Squad{} + err = c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id=?"), groupChats[i].ID) + if err != nil { + c.Log.Debug(err) + } else { + chatSquad.ChatRole = "squad" + } + err = c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE flood_chat_id=?"), groupChats[i].ID) + if err != nil { + c.Log.Debug(err) + } else { + chatSquad.ChatRole = "flood" + } + + chatSquad.Squad = squad + chatSquad.Chat = groupChats[i] + + chatsSquads = append(chatsSquads, chatSquad) + } + + return chatsSquads, true +} + // GetChatByID returns dbmapping.Chat instance with given ID. -func (g *Getters) GetChatByID(chatID int64) (dbmapping.Chat, bool) { +func (ct *Chatter) GetChatByID(chatID int64) (dbmapping.Chat, bool) { chatRaw := dbmapping.Chat{} err := c.Db.Get(&chatRaw, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), chatID) if err != nil { @@ -23,7 +58,7 @@ func (g *Getters) GetChatByID(chatID int64) (dbmapping.Chat, bool) { // GetOrCreateChat seeks for chat in database via Telegram update. // In case, when there is no chat with such ID, new chat will be created. -func (g *Getters) GetOrCreateChat(telegramUpdate *tgbotapi.Update) (dbmapping.Chat, bool) { +func (ct *Chatter) GetOrCreateChat(telegramUpdate *tgbotapi.Update) (dbmapping.Chat, bool) { chatRaw := dbmapping.Chat{} c.Log.Debug("TGID: ", telegramUpdate.Message.Chat.ID) err := c.Db.Get(&chatRaw, c.Db.Rebind("SELECT * FROM chats WHERE telegram_id=?"), telegramUpdate.Message.Chat.ID) @@ -68,7 +103,7 @@ func (g *Getters) GetOrCreateChat(telegramUpdate *tgbotapi.Update) (dbmapping.Ch } // GetAllPrivateChats returns all private chats -func (g *Getters) GetAllPrivateChats() ([]dbmapping.Chat, bool) { +func (ct *Chatter) GetAllPrivateChats() ([]dbmapping.Chat, bool) { privateChats := []dbmapping.Chat{} err := c.Db.Select(&privateChats, "SELECT * FROM chats WHERE chat_type='private'") @@ -81,7 +116,7 @@ func (g *Getters) GetAllPrivateChats() ([]dbmapping.Chat, bool) { } // GetAllGroupChats returns all group chats -func (g *Getters) GetAllGroupChats() ([]dbmapping.Chat, bool) { +func (ct *Chatter) GetAllGroupChats() ([]dbmapping.Chat, bool) { groupChats := []dbmapping.Chat{} err := c.Db.Select(&groupChats, "SELECT * FROM chats WHERE chat_type IN ('group', 'supergroup')") @@ -91,47 +126,4 @@ func (g *Getters) GetAllGroupChats() ([]dbmapping.Chat, bool) { } return groupChats, true -} - -// GetAllGroupChatsWithSquads returns all group chats with squads -func (g *Getters) GetAllGroupChatsWithSquads() ([]dbmapping.SquadChat, bool) { - chatsSquads := []dbmapping.SquadChat{} - groupChats := []dbmapping.Chat{} - - err := c.Db.Select(&groupChats, "SELECT * FROM chats WHERE chat_type IN ('group', 'supergroup')") - if err != nil { - c.Log.Error(err) - return chatsSquads, false - } - - for i := range groupChats { - chatSquad := dbmapping.SquadChat{} - squad := dbmapping.Squad{} - err = c.Db.Select(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id="), groupChats[i].ID) - if err != nil { - c.Log.Debug(err) - chatSquad.IsSquad = false - } else { - chatSquad.IsSquad = true - } - - chatSquad.Squad = squad - chatSquad.Chat = groupChats[i] - - chatsSquads = append(chatsSquads, chatSquad) - } - - return chatsSquads, true -} - -// UpdateChatTitle updates chat title in database -func (g *Getters) UpdateChatTitle(chatRaw *dbmapping.Chat, newTitle string) (*dbmapping.Chat, bool) { - chatRaw.Name = newTitle - _, err := c.Db.NamedExec("UPDATE chats SET name=:name WHERE id=:id", &chatRaw) - if err != nil { - c.Log.Error(err) - return chatRaw, false - } - - return chatRaw, true -} +} \ No newline at end of file diff --git a/lib/chatter/responders.go b/lib/chatter/responders.go new file mode 100644 index 0000000..834e68b --- /dev/null +++ b/lib/chatter/responders.go @@ -0,0 +1,42 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package chatter + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "strconv" +) + +// GroupsList lists all chats where bot exist +func (ct *Chatter) GroupsList(update *tgbotapi.Update) string { + groupChats, ok := ct.getAllGroupChatsWithSquads() + if !ok { + return "fail" + } + + message := "*Бот состоит в следующих групповых чатах:*\n" + + for i := range groupChats { + message += "---\n" + message += "[#" + strconv.Itoa(groupChats[i].Chat.ID) + "] _" + groupChats[i].Chat.Name + "_\n" + message += "Telegram ID: " + strconv.FormatInt(groupChats[i].Chat.TelegramID, 10) + "\n" + if groupChats[i].ChatRole == "squad" { + message += "Статистика отряда:\n" + message += c.Squader.SquadStatictics(groupChats[i].Squad.ID) + } else if groupChats[i].ChatRole == "flood" { + message += "Является флудочатом отряда №" + strconv.Itoa(groupChats[i].Squad.ID) + "\n" + } else { + message += "Не является отрядом.\n" + } + } + + message += "\nЧтобы создать отряд, введите команду /make\\_squad _X Y_, где _X_ — номер чата с пинами (в нём позволено писать лишь боту и командирам), а _Y_ — чат-флудилка для общения отряда." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} diff --git a/lib/chatter/updaters.go b/lib/chatter/updaters.go new file mode 100644 index 0000000..f949e45 --- /dev/null +++ b/lib/chatter/updaters.go @@ -0,0 +1,43 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package chatter + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +// UpdateChatTitle updates chat title in database +func (ct *Chatter) UpdateChatTitle(chatRaw *dbmapping.Chat, newTitle string) (*dbmapping.Chat, bool) { + chatRaw.Name = newTitle + _, err := c.Db.NamedExec("UPDATE chats SET name=:name WHERE id=:id", &chatRaw) + if err != nil { + c.Log.Error(err) + return chatRaw, false + } + + return chatRaw, true +} + +// UpdateChatTelegramID updates chat's TelegramID when it converts to supergroup +func (ct *Chatter) UpdateChatTelegramID(update *tgbotapi.Update) (*dbmapping.Chat, bool) { + c.Log.Debug("Updating existing Telegram chat ID...") + chatRaw := dbmapping.Chat{} + err := c.Db.Get(&chatRaw, c.Db.Rebind("SELECT * FROM chats WHERE telegram_id=?"), update.Message.MigrateFromChatID) + if err != nil { + c.Log.Error(err.Error()) + return &chatRaw, false + } + if update.Message.SuperGroupChatCreated { + chatRaw.ChatType = "supergroup" + } + chatRaw.TelegramID = update.Message.MigrateToChatID + _, err = c.Db.NamedExec("UPDATE chats SET chat_type=:chat_type, telegram_id=:telegram_id WHERE id=:id", &chatRaw) + if err != nil { + c.Log.Error(err.Error()) + return &chatRaw, false + } + + return &chatRaw, true +} diff --git a/lib/dbmapping/chats.go b/lib/dbmapping/chats.go index 03443be..552f0f8 100644 --- a/lib/dbmapping/chats.go +++ b/lib/dbmapping/chats.go @@ -15,3 +15,10 @@ type Chat struct { TelegramID int64 `db:"telegram_id"` CreatedAt time.Time `db:"created_at"` } + +// ChatSquad is a stuct, which combines information about chats and squads +type ChatSquad struct { + Chat Chat + Squad Squad + ChatRole string +} diff --git a/lib/dbmapping/squads.go b/lib/dbmapping/squads.go index 01f4983..e7facdb 100644 --- a/lib/dbmapping/squads.go +++ b/lib/dbmapping/squads.go @@ -9,15 +9,16 @@ import ( // Squad is a struct, which represents `squads` table item in databse. type Squad struct { - ID int `db:"id"` - ChatID int `db:"chat_id"` - AuthorID int `db:"author_id"` - CreatedAt time.Time `db:"created_at"` + ID int `db:"id"` + ChatID int `db:"chat_id"` + FloodChatID int `db:"flood_chat_id"` + AuthorID int `db:"author_id"` + CreatedAt time.Time `db:"created_at"` } // SquadChat is a stuct, which combines information about chats and squads type SquadChat struct { - Squad Squad - Chat Chat - IsSquad bool + Squad Squad + Chat Chat + FloodChat Chat } diff --git a/lib/dbmapping/squads_players.go b/lib/dbmapping/squads_players.go index 62d472e..e849051 100644 --- a/lib/dbmapping/squads_players.go +++ b/lib/dbmapping/squads_players.go @@ -15,3 +15,10 @@ type SquadPlayer struct { AuthorID int `db:"author_id"` CreatedAt time.Time `db:"created_at"` } + +// SquadPlayerFull is a struct, which handles all related information +type SquadPlayerFull struct { + Squad Squad + Player Player + Profile Profile +} diff --git a/lib/getters/gettersinterface/gettersinterface.go b/lib/getters/gettersinterface/gettersinterface.go index cb9a5a8..3736b28 100644 --- a/lib/getters/gettersinterface/gettersinterface.go +++ b/lib/getters/gettersinterface/gettersinterface.go @@ -4,7 +4,6 @@ package gettersinterface import ( - "github.com/go-telegram-bot-api/telegram-bot-api" "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" ) @@ -14,12 +13,6 @@ type GettersInterface interface { 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) - GetOrCreateChat(update *tgbotapi.Update) (dbmapping.Chat, bool) - GetChatByID(chatID int64) (dbmapping.Chat, bool) - GetAllPrivateChats() ([]dbmapping.Chat, bool) - GetAllGroupChats() ([]dbmapping.Chat, bool) - GetAllGroupChatsWithSquads() ([]dbmapping.SquadChat, bool) - UpdateChatTitle(chatRaw *dbmapping.Chat, newTitle string) (*dbmapping.Chat, bool) GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) GetPlayerByID(playerID int) (dbmapping.Player, bool) PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool diff --git a/lib/migrations/22_add_flood_chat_id.go b/lib/migrations/22_add_flood_chat_id.go new file mode 100644 index 0000000..7a800f3 --- /dev/null +++ b/lib/migrations/22_add_flood_chat_id.go @@ -0,0 +1,29 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package migrations + +import ( + // stdlib + "database/sql" +) + +// AddFloodChatIDUp creates `flood_chat_id` column in `squads` table +func AddFloodChatIDUp(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE `squads` ADD COLUMN `flood_chat_id` INT(11) NOT NULL DEFAULT 0 COMMENT 'ID группы для общения отряда' AFTER `chat_id`;") + if err != nil { + return err + } + + return nil +} + +// AddFloodChatIDDown destroys `flood_chat_id` column +func AddFloodChatIDDown(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE `squads` DROP COLUMN `flood_chat_id`;") + if err != nil { + return err + } + + return nil +} diff --git a/lib/migrations/migrations.go b/lib/migrations/migrations.go index d7dea07..6b5e9d2 100644 --- a/lib/migrations/migrations.go +++ b/lib/migrations/migrations.go @@ -32,6 +32,7 @@ func (m *Migrations) Init() { goose.AddNamedMigration("19_create_broadcasts.go", CreateBroadcastsUp, CreateBroadcastsDown) goose.AddNamedMigration("20_create_squads.go", CreateSquadsUp, CreateSquadsDown) goose.AddNamedMigration("21_change_telegram_id_column.go", ChangeTelegramIDColumnUp, ChangeTelegramIDColumnDown) + goose.AddNamedMigration("22_add_flood_chat_id.go", AddFloodChatIDUp, AddFloodChatIDDown) } // Migrate migrates database to current version diff --git a/lib/pinner/pinner.go b/lib/pinner/pinner.go index 49fa56a..a16fc73 100644 --- a/lib/pinner/pinner.go +++ b/lib/pinner/pinner.go @@ -15,7 +15,7 @@ func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string { return "fail" } - groupChats, ok := c.Getters.GetAllGroupChats() + groupChats, ok := c.Chatter.GetAllGroupChats() if !ok { return "fail" } diff --git a/lib/router/group_request.go b/lib/router/group_request.go index da47dc0..7fba87e 100644 --- a/lib/router/group_request.go +++ b/lib/router/group_request.go @@ -27,7 +27,17 @@ func (r *Router) routeGroupRequest(update *tgbotapi.Update, playerRaw *dbmapping } // New chat names if update.Message.NewChatTitle != "" { - _, ok := c.Getters.UpdateChatTitle(chatRaw, update.Message.NewChatTitle) + _, ok := c.Chatter.UpdateChatTitle(chatRaw, update.Message.NewChatTitle) + if ok { + return "ok" + } + + return "fail" + } + + // New chat IDs (usually on supergroup creation) + if (update.Message.MigrateToChatID != 0) && (update.Message.MigrateFromChatID != 0) { + _, ok := c.Chatter.UpdateChatTelegramID(update) if ok { return "ok" } diff --git a/lib/router/private_request.go b/lib/router/private_request.go index 7701cc2..c837edd 100644 --- a/lib/router/private_request.go +++ b/lib/router/private_request.go @@ -78,10 +78,25 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi return "fail" case update.Message.Command() == "group_chats": if c.Getters.PlayerBetterThan(playerRaw, "admin") { - c.Talkers.GroupsList(update) + c.Chatter.GroupsList(update) return "ok" } + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + case update.Message.Command() == "squads": + if c.Getters.PlayerBetterThan(playerRaw, "admin") { + c.Squader.SquadsList(update) + return "ok" + } + + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + case update.Message.Command() == "make_squad": + if c.Getters.PlayerBetterThan(playerRaw, "admin") { + return c.Squader.CreateSquad(update) + } + c.Talkers.AnyMessageUnauthorized(update) return "fail" case update.Message.Command() == "pin": diff --git a/lib/router/router.go b/lib/router/router.go index 7fa028d..621e20c 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -15,7 +15,7 @@ func (r *Router) RouteRequest(update *tgbotapi.Update) string { return "fail" } - chatRaw, ok := c.Getters.GetOrCreateChat(update) + chatRaw, ok := c.Chatter.GetOrCreateChat(update) if !ok { return "fail" } diff --git a/lib/squader/exported.go b/lib/squader/exported.go new file mode 100644 index 0000000..1c62e0a --- /dev/null +++ b/lib/squader/exported.go @@ -0,0 +1,28 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package squader + +import ( + "lab.pztrn.name/fat0troll/i2_bot/lib/appcontext" + "lab.pztrn.name/fat0troll/i2_bot/lib/squader/squaderinterface" +) + +var ( + c *appcontext.Context +) + +// Squader is a function-handling struct for package squader. +type Squader struct{} + +// New is an initialization function for appcontext +func New(ac *appcontext.Context) { + c = ac + s := &Squader{} + c.RegisterSquaderInterface(squaderinterface.SquaderInterface(s)) +} + +// Init is a initialization function for package +func (s *Squader) Init() { + c.Log.Info("Initializing Squader...") +} diff --git a/lib/squader/squader.go b/lib/squader/squader.go new file mode 100644 index 0000000..777eec8 --- /dev/null +++ b/lib/squader/squader.go @@ -0,0 +1,257 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package squader + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" + "regexp" + "strconv" + "strings" + "time" +) + +func (s *Squader) getAllSquadsWithChats() ([]dbmapping.SquadChat, bool) { + squadsWithChats := []dbmapping.SquadChat{} + squads := []dbmapping.Squad{} + + err := c.Db.Select(&squads, "SELECT * FROM squads") + if err != nil { + c.Log.Error(err) + return squadsWithChats, false + } + + for i := range squads { + chatSquad := dbmapping.SquadChat{} + chat := dbmapping.Chat{} + floodChat := dbmapping.Chat{} + err = c.Db.Get(&chat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), squads[i].ChatID) + if err != nil { + c.Log.Error(err) + return squadsWithChats, false + } + err = c.Db.Get(&floodChat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), squads[i].FloodChatID) + if err != nil { + c.Log.Error(err) + return squadsWithChats, false + } + + chatSquad.Squad = squads[i] + chatSquad.Chat = chat + chatSquad.FloodChat = floodChat + + squadsWithChats = append(squadsWithChats, chatSquad) + } + + return squadsWithChats, true +} + +func (s *Squader) createSquad(update *tgbotapi.Update, chatID int, floodChatID int) (dbmapping.Squad, string) { + squad := dbmapping.Squad{} + chat := dbmapping.Chat{} + floodChat := dbmapping.Chat{} + + // Checking if chats in database exist + err := c.Db.Get(&chat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), chatID) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + err = c.Db.Get(&floodChat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), floodChatID) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + + err2 := c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id IN (?, ?) OR flood_chat_id IN (?, ?)"), chat.ID, floodChat.ID, chat.ID, floodChat.ID) + if err2 == nil { + return squad, "dup" + } + c.Log.Debug(err2) + + err = c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id=? AND flood_chat_id=?"), chatID, floodChatID) + if err != nil { + c.Log.Debug(err) + + playerRaw, ok := c.Getters.GetOrCreatePlayer(update.Message.From.ID) + if !ok { + return squad, "fail" + } + + squad.AuthorID = playerRaw.ID + squad.ChatID = chatID + squad.FloodChatID = floodChatID + squad.CreatedAt = time.Now().UTC() + + _, err = c.Db.NamedExec("INSERT INTO `squads` VALUES(NULL, :chat_id, :flood_chat_id, :author_id, :created_at)", &squad) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + + err = c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id=? AND flood_chat_id=?"), chatID, floodChatID) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + + return squad, "ok" + } + + return squad, "dup" +} + +func (s *Squader) getSquadByChatID(update *tgbotapi.Update, chatID int) (dbmapping.Squad, string) { + squad := dbmapping.Squad{} + chat := dbmapping.Chat{} + + // Checking if chat in database exist + err := c.Db.Get(&chat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), chatID) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + + err = c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE chat_id=?"), chat.ID) + if err != nil { + c.Log.Error(err) + return squad, "fail" + } + + return squad, "ok" +} + +func (s *Squader) squadCreationDuplicate(update *tgbotapi.Update) string { + message := "*Отряд уже существует*\n" + message += "Проверьте, правильно ли вы ввели команду, и повторите попытку." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "fail" +} + +func (s *Squader) squadCreationFailure(update *tgbotapi.Update) string { + message := "*Не удалось добавить отряд в базу*\n" + message += "Проверьте, правильно ли вы ввели команду, и повторите попытку." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "fail" +} + +func (s *Squader) squadCreationSuccess(update *tgbotapi.Update) string { + message := "*Отряд успешно добавлен в базу*\n" + message += "Просмотреть список отрядов можно командой /squads." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "fail" +} + +// External functions + +// CreateSquad creates new squad from chat if not already exist +func (s *Squader) CreateSquad(update *tgbotapi.Update) string { + commandArugments := update.Message.CommandArguments() + argumentsRx := regexp.MustCompile(`(\d+)\s(\d+)`) + + if !argumentsRx.MatchString(commandArugments) { + return s.squadCreationFailure(update) + } + + chatNumbers := strings.Split(commandArugments, " ") + if len(chatNumbers) < 2 { + return s.squadCreationFailure(update) + } + chatID, _ := strconv.Atoi(chatNumbers[0]) + if chatID == 0 { + return s.squadCreationFailure(update) + } + floodChatID, _ := strconv.Atoi(chatNumbers[1]) + if floodChatID == 0 { + return s.squadCreationFailure(update) + } + + _, ok := s.createSquad(update, chatID, floodChatID) + if ok == "fail" { + return s.squadCreationFailure(update) + } else if ok == "dup" { + return s.squadCreationDuplicate(update) + } + + return s.squadCreationSuccess(update) +} + +// SquadsList lists all squads +func (s *Squader) SquadsList(update *tgbotapi.Update) string { + squads, ok := s.getAllSquadsWithChats() + if !ok { + return "fail" + } + + message := "*Наши отряды:*\n" + + for i := range squads { + message += "---\n" + message += "[#" + strconv.Itoa(squads[i].Squad.ID) + "] _" + squads[i].Chat.Name + message += "_ /show\\_squad" + strconv.Itoa(squads[i].Squad.ID) + "\n" + message += "Telegram ID: " + strconv.FormatInt(squads[i].Chat.TelegramID, 10) + "\n" + message += "Флудилка отряда: _" + squads[i].FloodChat.Name + "_\n" + message += "Статистика отряда:\n" + message += s.SquadStatictics(squads[i].Squad.ID) + } + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} + +// SquadStatictics generates statistics message snippet. Public due to usage in chats list +func (s *Squader) SquadStatictics(squadID int) string { + squadMembersWithInformation := []dbmapping.SquadPlayerFull{} + squadMembers := []dbmapping.SquadPlayer{} + squad := dbmapping.Squad{} + + err := c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE id=?"), squadID) + if err != nil { + c.Log.Error(err.Error()) + return "Отряда не существует!" + } + + err = c.Db.Select(&squadMembers, c.Db.Rebind("SELECT * FROM squads_players WHERE squad_id=?"), squadID) + if err != nil { + c.Log.Error(err.Error()) + return "Невозможно получить информацию о данном отряде. Возможно, он пуст или произошла ошибка." + } + + for i := range squadMembers { + fullInfo := dbmapping.SquadPlayerFull{} + + playerRaw, _ := c.Getters.GetPlayerByID(squadMembers[i].PlayerID) + profileRaw, _ := c.Getters.GetProfile(playerRaw.ID) + + fullInfo.Squad = squad + fullInfo.Player = playerRaw + fullInfo.Profile = profileRaw + + squadMembersWithInformation = append(squadMembersWithInformation, fullInfo) + } + + message := "Количество человек в отряде: " + strconv.Itoa(len(squadMembersWithInformation)) + message += "\n" + + return message +} diff --git a/lib/squader/squaderinterface/squaderinterface.go b/lib/squader/squaderinterface/squaderinterface.go new file mode 100644 index 0000000..b21aa6c --- /dev/null +++ b/lib/squader/squaderinterface/squaderinterface.go @@ -0,0 +1,16 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package squaderinterface + +import ( + "github.com/go-telegram-bot-api/telegram-bot-api" +) + +// SquaderInterface implements Squader for importing via appcontext. +type SquaderInterface interface { + Init() + CreateSquad(update *tgbotapi.Update) string + SquadsList(update *tgbotapi.Update) string + SquadStatictics(squadID int) string +} diff --git a/lib/talkers/broadcast.go b/lib/talkers/broadcast.go index 8562261..7fd63ea 100644 --- a/lib/talkers/broadcast.go +++ b/lib/talkers/broadcast.go @@ -4,10 +4,10 @@ package talkers import ( - "strconv" - "strings" "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 @@ -63,7 +63,7 @@ func (t *Talkers) AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw * broadcastingMessageBody := messageRaw.Text - privateChats, ok := c.Getters.GetAllPrivateChats() + privateChats, ok := c.Chatter.GetAllPrivateChats() if !ok { return "fail" } diff --git a/lib/talkers/chats.go b/lib/talkers/chats.go deleted file mode 100644 index 4f30bb4..0000000 --- a/lib/talkers/chats.go +++ /dev/null @@ -1,37 +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" - "strconv" -) - -// GroupsList lists all chats where bot exist -func (t *Talkers) GroupsList(update *tgbotapi.Update) string { - groupChats, ok := c.Getters.GetAllGroupChatsWithSquads() - if !ok { - return "fail" - } - - message := "*Бот состоит в следующих групповых чатах:*\n" - - for i := range groupChats { - message += "---\n" - message += "[#" + strconv.Itoa(groupChats[i].Chat.ID) + "] _" + groupChats[i].Chat.Name + "_\n" - message += "Telegram ID: " + strconv.FormatInt(groupChats[i].Chat.TelegramID, 10) + "\n" - if groupChats[i].IsSquad { - message += "Является отрядом <статистика>\n" - } else { - message += "Не является отрядом. Сделать отрядом: /make\\_squad" + strconv.Itoa(groupChats[i].Chat.ID) + "\n" - } - } - - 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 96b7af6..fc35656 100644 --- a/lib/talkers/help.go +++ b/lib/talkers/help.go @@ -20,6 +20,7 @@ func (t *Talkers) HelpMessage(update *tgbotapi.Update, playerRaw *dbmapping.Play if c.Getters.PlayerBetterThan(playerRaw, "admin") { message += "+ /send\\_all _текст_ — отправить сообщение всем пользователям бота\n" message += "+ /group\\_chats — получить список групп, в которых работает бот.\n" + message += "+ /squads — получить список отрядов.\n" message += "+ /pin _текст_ — отправить сообщение во все группы, где находится бот. Сообщение будет автоматически запинено.\n" } message += "+ /help – выводит данное сообщение\n" diff --git a/lib/talkers/talkersinterface/talkersinterface.go b/lib/talkers/talkersinterface/talkersinterface.go index ef0b0dd..844db2c 100644 --- a/lib/talkers/talkersinterface/talkersinterface.go +++ b/lib/talkers/talkersinterface/talkersinterface.go @@ -31,8 +31,6 @@ type TalkersInterface interface { AdminBroadcastMessageCompose(update *tgbotapi.Update, playerRaw *dbmapping.Player) string AdminBroadcastMessageSend(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - GroupsList(update *tgbotapi.Update) string - DurakMessage(update *tgbotapi.Update) MatMessage(update *tgbotapi.Update) } diff --git a/lib/welcomer/notifications.go b/lib/welcomer/notifications.go index 17cb4be..b46727a 100644 --- a/lib/welcomer/notifications.go +++ b/lib/welcomer/notifications.go @@ -10,7 +10,7 @@ import ( func (w *Welcomer) alertUserWithoutProfile(update *tgbotapi.Update, newUser *tgbotapi.User) string { alertGroupID, _ := strconv.ParseInt(c.Cfg.Notifications.GroupID, 10, 64) - chat, ok := c.Getters.GetOrCreateChat(update) + chat, ok := c.Chatter.GetOrCreateChat(update) if !ok { return "fail" } @@ -29,7 +29,7 @@ func (w *Welcomer) alertUserWithoutProfile(update *tgbotapi.Update, newUser *tgb func (w *Welcomer) alertSpyUser(update *tgbotapi.Update, newUser *tgbotapi.User) string { alertGroupID, _ := strconv.ParseInt(c.Cfg.Notifications.GroupID, 10, 64) - chat, ok := c.Getters.GetOrCreateChat(update) + chat, ok := c.Chatter.GetOrCreateChat(update) if !ok { return "fail" }