From fef521e35b51c51f53c24c71999cef0549c1c41b Mon Sep 17 00:00:00 2001 From: Vladimir Hodakov Date: Sat, 17 Feb 2018 07:03:58 +0400 Subject: [PATCH] Squads and chats in DataCache, squads rework Work in progress, bugs may vary --- lib/broadcaster/sender.go | 12 +- .../chatterinterface/chatterinterface.go | 7 - lib/chatter/getters.go | 175 ------------ lib/chatter/responders.go | 26 +- lib/chatter/restricters.go | 19 +- lib/datacache/chats.go | 170 ++++++++++++ .../datacacheinterface/datacacheinterface.go | 19 ++ lib/datacache/exported.go | 14 + lib/datacache/squads.go | 248 +++++++++++++++++ lib/dbmapping/squads.go | 16 +- lib/migrations/18_add_pokememes_wealth.go | 2 +- lib/migrations/31_change_squads_table.go | 78 ++++++ lib/migrations/migrations.go | 1 + lib/orders/orders.go | 15 +- lib/pinner/pinner.go | 19 +- lib/router/private_request.go | 7 +- lib/router/router.go | 9 +- lib/squader/getters.go | 185 ------------- lib/squader/responders.go | 13 +- lib/squader/restricters.go | 28 -- lib/squader/squader.go | 255 +++--------------- .../squaderinterface/squaderinterface.go | 12 - lib/statistics/squads.go | 9 +- lib/users/responders.go | 6 +- lib/welcomer/notifications.go | 10 +- 25 files changed, 645 insertions(+), 710 deletions(-) delete mode 100644 lib/chatter/getters.go create mode 100644 lib/datacache/chats.go create mode 100644 lib/datacache/squads.go create mode 100644 lib/migrations/31_change_squads_table.go delete mode 100644 lib/squader/getters.go delete mode 100644 lib/squader/restricters.go diff --git a/lib/broadcaster/sender.go b/lib/broadcaster/sender.go index 265b6ce..4cfa5d4 100644 --- a/lib/broadcaster/sender.go +++ b/lib/broadcaster/sender.go @@ -4,8 +4,8 @@ package broadcaster import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "strconv" ) @@ -37,15 +37,9 @@ func (b *Broadcaster) AdminBroadcastMessageSend(update *tgbotapi.Update, playerR privateChats := []dbmapping.Chat{} switch messageRaw.BroadcastType { case "all": - privateChats, ok = c.Chatter.GetAllPrivateChats() - if !ok { - return "fail" - } + privateChats = c.DataCache.GetAllPrivateChats() case "league": - privateChats, ok = c.Chatter.GetLeaguePrivateChats() - if !ok { - return "fail" - } + privateChats = c.DataCache.GetLeaguePrivateChats() } for i := range privateChats { diff --git a/lib/chatter/chatterinterface/chatterinterface.go b/lib/chatter/chatterinterface/chatterinterface.go index 80e4418..f4ff79f 100644 --- a/lib/chatter/chatterinterface/chatterinterface.go +++ b/lib/chatter/chatterinterface/chatterinterface.go @@ -15,13 +15,6 @@ type ChatterInterface interface { BanUserFromChat(user *tgbotapi.User, chatRaw *dbmapping.Chat) ProtectChat(update *tgbotapi.Update, playerRaw *dbmapping.Player, chatRaw *dbmapping.Chat) string - GetOrCreateChat(update *tgbotapi.Update) (dbmapping.Chat, bool) - GetChatByID(chatID int64) (dbmapping.Chat, bool) - GetAllPrivateChats() ([]dbmapping.Chat, bool) - GetLeaguePrivateChats() ([]dbmapping.Chat, bool) - GetAllGroupChats() ([]dbmapping.Chat, bool) - GetGroupChatsByIDs(chatsIDs string) ([]dbmapping.Chat, bool) - UpdateChatTitle(chatRaw *dbmapping.Chat, newTitle string) (*dbmapping.Chat, bool) UpdateChatTelegramID(update *tgbotapi.Update) (*dbmapping.Chat, bool) diff --git a/lib/chatter/getters.go b/lib/chatter/getters.go deleted file mode 100644 index 39f92a6..0000000 --- a/lib/chatter/getters.go +++ /dev/null @@ -1,175 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package chatter - -import ( - "github.com/go-telegram-bot-api/telegram-bot-api" - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" - "strconv" - "strings" - "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 (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 { - c.Log.Error(err) - return chatRaw, false - } - - return chatRaw, true -} - -// 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 (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) - if err != nil { - c.Log.Error("Chat stream not found in database.") - c.Log.Error(err.Error()) - - nameOfChat := "" - if telegramUpdate.Message.Chat.FirstName != "" { - nameOfChat += telegramUpdate.Message.Chat.FirstName - } - if telegramUpdate.Message.Chat.LastName != "" { - nameOfChat += " " + telegramUpdate.Message.Chat.LastName - } - if telegramUpdate.Message.Chat.Title != "" { - if nameOfChat != "" { - nameOfChat += " [" + telegramUpdate.Message.Chat.Title + "]" - } else { - nameOfChat = telegramUpdate.Message.Chat.Title - } - } - - chatRaw.Name = nameOfChat - chatRaw.ChatType = telegramUpdate.Message.Chat.Type - chatRaw.TelegramID = telegramUpdate.Message.Chat.ID - chatRaw.CreatedAt = time.Now().UTC() - _, err = c.Db.NamedExec("INSERT INTO chats VALUES(NULL, :name, :chat_type, :telegram_id, :created_at)", &chatRaw) - if err != nil { - c.Log.Error(err.Error()) - return chatRaw, false - } - err2 := c.Db.Get(&chatRaw, c.Db.Rebind("SELECT * FROM chats WHERE telegram_id=? AND chat_type=?"), chatRaw.TelegramID, chatRaw.ChatType) - if err2 != nil { - c.Log.Error(err2) - return chatRaw, false - } - } else { - c.Log.Info("Chat stream found in database.") - } - - return chatRaw, true -} - -// GetAllPrivateChats returns all private chats -func (ct *Chatter) GetAllPrivateChats() ([]dbmapping.Chat, bool) { - privateChats := []dbmapping.Chat{} - - err := c.Db.Select(&privateChats, "SELECT * FROM chats WHERE chat_type='private'") - if err != nil { - c.Log.Error(err) - return privateChats, false - } - - return privateChats, true -} - -// GetLeaguePrivateChats returns all private chats which profiles are in our league -func (ct *Chatter) GetLeaguePrivateChats() ([]dbmapping.Chat, bool) { - privateChats := []dbmapping.Chat{} - - err := c.Db.Select(&privateChats, "SELECT c.* FROM chats c, players p WHERE c.chat_type='private' AND p.telegram_id = c.telegram_id AND p.league_id = 1 AND p.status != 'spy' AND p.status != 'league_changed' AND p.status !='banned'") - if err != nil { - c.Log.Error(err) - return privateChats, false - } - - return privateChats, true -} - -// GetAllGroupChats returns all group chats -func (ct *Chatter) GetAllGroupChats() ([]dbmapping.Chat, bool) { - 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 groupChats, false - } - - return groupChats, true -} - -// GetGroupChatsByIDs returns group chats with selected IDs -func (ct *Chatter) GetGroupChatsByIDs(chatsIDs string) ([]dbmapping.Chat, bool) { - groupChats := []dbmapping.Chat{} - - queryIDs := make([]int, 0) - queryIDsStr := strings.Split(chatsIDs, ",") - for i := range queryIDsStr { - id, _ := strconv.Atoi(queryIDsStr[i]) - if id != 0 { - queryIDs = append(queryIDs, id) - } - } - - finalQueryIDs := "" - for i := range queryIDs { - finalQueryIDs += strconv.Itoa(queryIDs[i]) - if i < len(queryIDs)-1 { - finalQueryIDs += "," - } - } - c.Log.Debug("Chat query IDs: " + finalQueryIDs) - - err := c.Db.Select(&groupChats, "SELECT * FROM chats WHERE chat_type IN ('group', 'supergroup') AND id IN ("+finalQueryIDs+")") - if err != nil { - c.Log.Error(err) - return groupChats, false - } - - return groupChats, true -} diff --git a/lib/chatter/responders.go b/lib/chatter/responders.go index 3b6861c..ec65a1a 100644 --- a/lib/chatter/responders.go +++ b/lib/chatter/responders.go @@ -10,10 +10,7 @@ import ( // GroupsList lists all chats where bot exist func (ct *Chatter) GroupsList(update *tgbotapi.Update) string { - groupChats, ok := ct.getAllGroupChatsWithSquads() - if !ok { - return "fail" - } + groupChats := c.DataCache.GetAllGroupChats() academyChatID, _ := strconv.ParseInt(c.Cfg.SpecialChats.AcademyID, 10, 64) bastionChatID, _ := strconv.ParseInt(c.Cfg.SpecialChats.BastionID, 10, 64) @@ -24,33 +21,30 @@ func (ct *Chatter) GroupsList(update *tgbotapi.Update) string { for i := range groupChats { message += "---\n" - message += "\\[#" + strconv.Itoa(groupChats[i].Chat.ID) + "] _" + c.Users.FormatUsername(groupChats[i].Chat.Name) + "_\n" - message += "Telegram ID: " + strconv.FormatInt(groupChats[i].Chat.TelegramID, 10) + "\n" - if groupChats[i].ChatRole == "squad" { + message += "\\[#" + strconv.Itoa(groupChats[i].ID) + "] _" + c.Users.FormatUsername(groupChats[i].Name) + "_\n" + message += "Telegram ID: " + strconv.FormatInt(groupChats[i].TelegramID, 10) + "\n" + squad, squadExistErr := c.DataCache.GetSquadByChatID(groupChats[i].ID) + if squadExistErr == nil { message += "Статистика отряда:\n" - message += c.Statistics.SquadStatictics(groupChats[i].Squad.ID) - } else if groupChats[i].ChatRole == "flood" { - message += "Является флудочатом отряда №" + strconv.Itoa(groupChats[i].Squad.ID) + "\n" + message += c.Statistics.SquadStatictics(squad.ID) } else { - if groupChats[i].Chat.TelegramID == academyChatID { + if groupChats[i].TelegramID == academyChatID { message += "Является академией лиги\n" } - if groupChats[i].Chat.TelegramID == bastionChatID { + if groupChats[i].TelegramID == bastionChatID { message += "Является бастионом лиги\n" } - if groupChats[i].Chat.TelegramID == defaultChatID { + if groupChats[i].TelegramID == defaultChatID { message += "Является чатом по умолчанию лиги\n" } - if groupChats[i].Chat.TelegramID == hqChatID { + if groupChats[i].TelegramID == hqChatID { message += "Является чатом совета лиги\n" } } } - message += "\nЧтобы создать отряд, введите команду /make\\_squad _X Y_, где _X_ — номер чата с пинами (в нём позволено писать лишь боту и командирам), а _Y_ — чат-флудилка для общения отряда." - msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) msg.ParseMode = "Markdown" diff --git a/lib/chatter/restricters.go b/lib/chatter/restricters.go index 8c4740f..3654e1f 100644 --- a/lib/chatter/restricters.go +++ b/lib/chatter/restricters.go @@ -4,8 +4,8 @@ package chatter import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "strconv" "strings" ) @@ -44,15 +44,15 @@ func (ct *Chatter) userPrivilegesCheck(update *tgbotapi.Update, user *tgbotapi.U // So, user is not a PokememBro admin. For Bastion and Academy she needs to be league player switch update.Message.Chat.ID { case academyChatID: - if playerRaw.LeagueID == 1 && playerRaw.Status != "spy" && playerRaw.Status != "league_changed" { + if playerRaw.LeagueID == 1 && playerRaw.Status != "spy" && playerRaw.Status != "league_changed" && playerRaw.Status != "banned" { return true } case bastionChatID: - if playerRaw.LeagueID == 1 && playerRaw.Status != "spy" && playerRaw.Status != "league_changed" { + if playerRaw.LeagueID == 1 && playerRaw.Status != "spy" && playerRaw.Status != "league_changed" && playerRaw.Status != "banned" { return true } default: - availableChatsForUser, _ := c.Squader.GetAvailableSquadChatsForUser(playerRaw) + availableChatsForUser := c.DataCache.GetAvailableSquadsChatsForUser(playerRaw.ID) for i := range availableChatsForUser { if update.Message.Chat.ID == availableChatsForUser[i].TelegramID { return true @@ -86,9 +86,12 @@ func (ct *Chatter) BanUserFromChat(user *tgbotapi.User, chatRaw *dbmapping.Chat) academyChatID, _ := strconv.ParseInt(c.Cfg.SpecialChats.AcademyID, 10, 64) hqChatID, _ := strconv.ParseInt(c.Cfg.SpecialChats.HeadquartersID, 10, 64) if (chatRaw.TelegramID != bastionChatID) || (chatRaw.TelegramID != academyChatID) { - // In Bastion notifications are public in default chat - commanders, ok := c.Squader.GetCommandersForSquadViaChat(chatRaw) - if ok { + squad, err := c.DataCache.GetSquadByChatID(chatRaw.ID) + if err != nil { + c.Log.Error(err.Error()) + } else { + // In Bastion notifications are public in default chat + commanders := c.DataCache.GetCommandersForSquad(squad.ID) for i := range commanders { message := "Некто " + c.Users.GetPrettyName(user) + " попытался зайти в чат _" + chatRaw.Name + "_ и был изгнан ботом, так как не имеет права посещать этот чат." @@ -128,5 +131,5 @@ func (ct *Chatter) ProtectChat(update *tgbotapi.Update, playerRaw *dbmapping.Pla return "fail" } - return c.Squader.CleanFlood(update, chatRaw) + return "ok" } diff --git a/lib/datacache/chats.go b/lib/datacache/chats.go new file mode 100644 index 0000000..beb9396 --- /dev/null +++ b/lib/datacache/chats.go @@ -0,0 +1,170 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "github.com/go-telegram-bot-api/telegram-bot-api" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" + "strconv" + "time" +) + +func (dc *DataCache) initChats() { + c.Log.Info("Initializing Chats storage...") + dc.chats = make(map[int]*dbmapping.Chat) +} + +func (dc *DataCache) loadChats() { + c.Log.Info("Load current Chats data from database to DataCache...") + chats := []dbmapping.Chat{} + err := c.Db.Select(&chats, "SELECT * FROM chats") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + dc.chatsMutex.Lock() + for i := range chats { + dc.chats[chats[i].ID] = &chats[i] + } + c.Log.Info("Loaded chats in DataCache: " + strconv.Itoa(len(dc.chats))) + dc.chatsMutex.Unlock() +} + +// External function + +// GetAllGroupChats returns all bot's group chats +func (dc *DataCache) GetAllGroupChats() []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.chatsMutex.Lock() + for i := range dc.chats { + if dc.chats[i].ChatType == "group" || dc.chats[i].ChatType == "supergroup" { + chats = append(chats, *dc.chats[i]) + } + } + dc.chatsMutex.Unlock() + + return chats +} + +// GetAllPrivateChats returns all bot's private chats +func (dc *DataCache) GetAllPrivateChats() []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.chatsMutex.Lock() + for i := range dc.chats { + if dc.chats[i].ChatType == "private" { + chats = append(chats, *dc.chats[i]) + } + } + dc.chatsMutex.Unlock() + + return chats +} + +// GetChatByID returns Chat by it's ID +func (dc *DataCache) GetChatByID(chatID int) (*dbmapping.Chat, error) { + if dc.chats[chatID] != nil { + return dc.chats[chatID], nil + } + + return nil, errors.New("There is no chat with ID=" + strconv.Itoa(chatID)) +} + +// GetGroupChatsByIDs returns bot's group chats with given IDs +func (dc *DataCache) GetGroupChatsByIDs(chatIDs []int) []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.chatsMutex.Lock() + for i := range dc.chats { + if dc.chats[i].ChatType == "group" || dc.chats[i].ChatType == "supergroup" { + for j := range chatIDs { + if dc.chats[i].ID == j { + chats = append(chats, *dc.chats[i]) + } + } + } + } + dc.chatsMutex.Unlock() + + return chats +} + +// GetLeaguePrivateChats returns private chats for all league members +func (dc *DataCache) GetLeaguePrivateChats() []dbmapping.Chat { + dc.playersMutex.Lock() + dc.chatsMutex.Lock() + + chats := []dbmapping.Chat{} + + for i := range dc.players { + if dc.players[i].Status != "banned" && dc.players[i].Status != "spy" && dc.players[i].Status != "league_changed" && dc.players[i].LeagueID == 1 { + if dc.chats[dc.players[i].TelegramID] != nil { + chats = append(chats, *dc.chats[dc.players[i].TelegramID]) + } + } + } + + dc.playersMutex.Unlock() + dc.chatsMutex.Unlock() + + return chats +} + +// GetOrCreateChat returns current or new Chat object by Telegram update +func (dc *DataCache) GetOrCreateChat(update *tgbotapi.Update) (*dbmapping.Chat, error) { + telegramID := update.Message.From.ID + chatRaw := dbmapping.Chat{} + c.Log.Info("DataCache: Getting chat with Telegram ID=", telegramID) + + dc.chatsMutex.Lock() + for i := range dc.chats { + if dc.chats[i].TelegramID == int64(telegramID) { + dc.chatsMutex.Unlock() + return dc.chats[i], nil + } + } + dc.chatsMutex.Unlock() + + // If we're here: there is no chat with given Telegram ID + c.Log.Error("Chat stream not found in DataCache. Adding chat...") + + nameOfChat := "" + if update.Message.Chat.FirstName != "" { + nameOfChat += update.Message.Chat.FirstName + } + if update.Message.Chat.LastName != "" { + nameOfChat += " " + update.Message.Chat.LastName + } + if update.Message.Chat.Title != "" { + if nameOfChat != "" { + nameOfChat += " [" + update.Message.Chat.Title + "]" + } else { + nameOfChat = update.Message.Chat.Title + } + } + + chatRaw.Name = nameOfChat + chatRaw.ChatType = update.Message.Chat.Type + chatRaw.TelegramID = update.Message.Chat.ID + chatRaw.CreatedAt = time.Now().UTC() + _, err := c.Db.NamedExec("INSERT INTO chats VALUES(NULL, :name, :chat_type, :telegram_id, :created_at)", &chatRaw) + if err != nil { + c.Log.Error(err.Error()) + return nil, err + } + err = c.Db.Get(&chatRaw, c.Db.Rebind("SELECT * FROM chats WHERE telegram_id=? AND chat_type=?"), chatRaw.TelegramID, chatRaw.ChatType) + if err != nil { + c.Log.Error(err) + return nil, err + } + + dc.chatsMutex.Lock() + dc.chats[chatRaw.ID] = &chatRaw + dc.chatsMutex.Unlock() + + return &chatRaw, nil +} diff --git a/lib/datacache/datacacheinterface/datacacheinterface.go b/lib/datacache/datacacheinterface/datacacheinterface.go index bb7fbd2..db42457 100644 --- a/lib/datacache/datacacheinterface/datacacheinterface.go +++ b/lib/datacache/datacacheinterface/datacacheinterface.go @@ -4,6 +4,7 @@ package datacacheinterface import ( + "github.com/go-telegram-bot-api/telegram-bot-api" "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" ) @@ -11,6 +12,24 @@ import ( type DataCacheInterface interface { Init() + GetAllGroupChats() []dbmapping.Chat + GetAllPrivateChats() []dbmapping.Chat + GetChatByID(chatID int) (*dbmapping.Chat, error) + GetOrCreateChat(update *tgbotapi.Update) (*dbmapping.Chat, error) + GetGroupChatsByIDs(chatIDs []int) []dbmapping.Chat + GetLeaguePrivateChats() []dbmapping.Chat + + AddPlayerToSquad(relation *dbmapping.SquadPlayer) (int, error) + GetAllSquadsChats() []dbmapping.Chat + GetAllSquadsWithChats() []dbmapping.SquadChat + GetAvailableSquadsChatsForUser(userID int) []dbmapping.Chat + GetCommandersForSquad(squadID int) []dbmapping.Player + GetSquadByID(squadID int) (*dbmapping.SquadChat, error) + GetSquadByChatID(chatID int) (*dbmapping.Squad, error) + GetSquadsChatsBySquadsIDs(squadsIDs []int) []dbmapping.Chat + GetUserRoleInSquad(squadID int, playerID int) string + GetUserRolesInSquads(userID int) []dbmapping.SquadPlayerFull + AddPlayer(player *dbmapping.Player) (int, error) GetOrCreatePlayerByTelegramID(telegramID int) (*dbmapping.Player, error) GetPlayerByID(playerID int) (*dbmapping.Player, error) diff --git a/lib/datacache/exported.go b/lib/datacache/exported.go index 2ed283f..4ea138a 100644 --- a/lib/datacache/exported.go +++ b/lib/datacache/exported.go @@ -34,6 +34,16 @@ type DataCache struct { fullPokememes map[int]*dbmapping.PokememeFull fullPokememesMutex sync.Mutex + // Chats + chats map[int]*dbmapping.Chat + chatsMutex sync.Mutex + // Squads + squads map[int]*dbmapping.Squad + squadsWithChats map[int]*dbmapping.SquadChat + squadPlayersRelations map[int]*dbmapping.SquadPlayer + squadPlayers map[int]map[int]*dbmapping.SquadPlayerFull + squadsMutex sync.Mutex + // Elements elements map[int]*dbmapping.Element elementsMutex sync.Mutex @@ -73,4 +83,8 @@ func (dc *DataCache) Init() { dc.loadPlayers() dc.initProfiles() dc.loadProfiles() + dc.initChats() + dc.loadChats() + dc.initSquads() + dc.loadSquads() } diff --git a/lib/datacache/squads.go b/lib/datacache/squads.go new file mode 100644 index 0000000..d33e632 --- /dev/null +++ b/lib/datacache/squads.go @@ -0,0 +1,248 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2018 Vladimir "fat0troll" Hodakov + +package datacache + +import ( + "errors" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" + "strconv" +) + +func (dc *DataCache) initSquads() { + c.Log.Info("Initializing Squads storage...") + dc.squads = make(map[int]*dbmapping.Squad) + dc.squadsWithChats = make(map[int]*dbmapping.SquadChat) + dc.squadPlayers = make(map[int]map[int]*dbmapping.SquadPlayerFull) + dc.squadPlayersRelations = make(map[int]*dbmapping.SquadPlayer) +} + +func (dc *DataCache) loadSquads() { + c.Log.Info("Load current Squads data from database to DataCache...") + squads := []dbmapping.Squad{} + err := c.Db.Select(&squads, "SELECT * FROM squads") + if err != nil { + // This is critical error and we need to stop immediately! + c.Log.Fatal(err.Error()) + } + + squadsPlayersRelations := []dbmapping.SquadPlayer{} + err = c.Db.Select(&squadsPlayersRelations, "SELECT * FROM squads_players") + if err != nil { + c.Log.Fatal(err.Error()) + } + + dc.squadsMutex.Lock() + for i := range squads { + squadWithChat := dbmapping.SquadChat{} + squadWithChat.Squad = squads[i] + sChat := dc.chats[squads[i].ChatID] + if sChat != nil { + squadWithChat.Chat = *sChat + + dc.squads[squads[i].ID] = &squads[i] + dc.squadsWithChats[squads[i].ID] = &squadWithChat + } + } + + for i := range squadsPlayersRelations { + sPlayer := dc.players[squadsPlayersRelations[i].PlayerID] + sProfile := dc.currentProfiles[squadsPlayersRelations[i].PlayerID] + sSquad := dc.squadsWithChats[squadsPlayersRelations[i].PlayerID] + if sPlayer != nil && sProfile != nil && sSquad != nil { + dc.squadPlayersRelations[squadsPlayersRelations[i].ID] = &squadsPlayersRelations[i] + + squadPlayer := dbmapping.SquadPlayerFull{} + squadPlayer.Player = *sPlayer + squadPlayer.Profile = *sProfile + squadPlayer.Squad = *sSquad + squadPlayer.UserRole = squadsPlayersRelations[i].UserType + + if dc.squadPlayers[sSquad.Squad.ID] == nil { + dc.squadPlayers[sSquad.Squad.ID] = make(map[int](*dbmapping.SquadPlayerFull)) + } + + dc.squadPlayers[sSquad.Squad.ID][sPlayer.ID] = &squadPlayer + } + } + c.Log.Info("Loaded squads in DataCache: " + strconv.Itoa(len(dc.squads))) + c.Log.Info("Loaded players relations to squads in DataCache: " + strconv.Itoa(len(dc.squadPlayers))) + dc.squadsMutex.Unlock() +} + +// External functions + +// AddPlayerToSquad creates relation between player and squad +func (dc *DataCache) AddPlayerToSquad(relation *dbmapping.SquadPlayer) (int, error) { + sPlayer, err := c.DataCache.GetPlayerByID(relation.PlayerID) + if err != nil { + return 0, err + } + sProfile, err := c.DataCache.GetProfileByPlayerID(relation.PlayerID) + if err != nil { + return 0, err + } + sSquad, err := c.DataCache.GetSquadByID(relation.SquadID) + if err != nil { + return 0, err + } + dc.squadsMutex.Lock() + for i := range dc.squadPlayersRelations { + if dc.squadPlayersRelations[i].SquadID == relation.SquadID && dc.squadPlayersRelations[i].PlayerID == relation.PlayerID { + dc.squadsMutex.Unlock() + return 0, errors.New("There is already such a player-squad relation") + } + } + dc.squadsMutex.Unlock() + + _, err = c.Db.NamedExec("INSERT INTO squads_players VALUES(NULL, :squad_id, :player_id, :user_type, :author_id, :created_at)", &relation) + if err != nil { + return 0, err + } + + insertedRelation := dbmapping.SquadPlayer{} + err = c.Db.Get(&insertedRelation, "SELECT * FROM squads_players WHERE squad_id=? AND player_id=?", relation.SquadID, relation.PlayerID) + if err != nil { + return 0, err + } + + dc.squadsMutex.Lock() + dc.squadPlayersRelations[insertedRelation.ID] = &insertedRelation + squadPlayerFull := dbmapping.SquadPlayerFull{} + squadPlayerFull.Player = *sPlayer + squadPlayerFull.Profile = *sProfile + squadPlayerFull.Squad = *sSquad + squadPlayerFull.UserRole = insertedRelation.UserType + if dc.squadPlayers[sSquad.Squad.ID] == nil { + dc.squadPlayers[sSquad.Squad.ID] = make(map[int]*dbmapping.SquadPlayerFull) + } + dc.squadPlayers[sSquad.Squad.ID][sPlayer.ID] = &squadPlayerFull + dc.squadsMutex.Unlock() + + return insertedRelation.ID, nil +} + +// GetAllSquadsChats returns all chats belonging to squads +func (dc *DataCache) GetAllSquadsChats() []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.squadsMutex.Lock() + for i := range dc.squadsWithChats { + chats = append(chats, dc.squadsWithChats[i].Chat) + } + dc.squadsMutex.Unlock() + + return chats +} + +// GetAllSquadsWithChats returns all squads with chats +func (dc *DataCache) GetAllSquadsWithChats() []dbmapping.SquadChat { + squadsWithChats := []dbmapping.SquadChat{} + + dc.squadsMutex.Lock() + for i := range dc.squadsWithChats { + squadsWithChats = append(squadsWithChats, *dc.squadsWithChats[i]) + } + dc.squadsMutex.Unlock() + + return squadsWithChats +} + +// GetAvailableSquadsChatsForUser returns all squads chats accessible by user with given ID +func (dc *DataCache) GetAvailableSquadsChatsForUser(userID int) []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.squadsMutex.Lock() + for i := range dc.squadPlayers { + for j := range dc.squadPlayers[i] { + if dc.squadPlayers[i][j].Player.ID == userID { + chats = append(chats, dc.squadPlayers[i][j].Squad.Chat) + } + } + } + dc.squadsMutex.Unlock() + + return chats +} + +// GetCommandersForSquad returns all players which are commanders of squad with given ID +func (dc *DataCache) GetCommandersForSquad(squadID int) []dbmapping.Player { + commanders := []dbmapping.Player{} + + dc.squadsMutex.Lock() + for i := range dc.squadPlayers[squadID] { + if dc.squadPlayers[squadID][i].Squad.Squad.ID == squadID { + if dc.squadPlayers[squadID][i].UserRole == "commander" { + commanders = append(commanders, dc.squadPlayers[squadID][i].Player) + } + } + } + dc.squadsMutex.Unlock() + + return commanders +} + +// GetSquadByID returns squad with given ID +func (dc *DataCache) GetSquadByID(squadID int) (*dbmapping.SquadChat, error) { + if dc.squadsWithChats[squadID] != nil { + return dc.squadsWithChats[squadID], nil + } + + return nil, errors.New("There is no squad with ID=" + strconv.Itoa(squadID)) +} + +// GetSquadByChatID returns squad with given chat ID +func (dc *DataCache) GetSquadByChatID(chatID int) (*dbmapping.Squad, error) { + dc.squadsMutex.Lock() + for i := range dc.squadsWithChats { + if dc.squadsWithChats[i].Chat.ID == chatID { + dc.squadsMutex.Unlock() + return dc.squads[i], nil + } + } + dc.squadsMutex.Unlock() + return nil, errors.New("There is no squad with chat ID=" + strconv.Itoa(chatID)) +} + +// GetSquadsChatsBySquadsIDs returns chats for given squad IDs +func (dc *DataCache) GetSquadsChatsBySquadsIDs(squadsIDs []int) []dbmapping.Chat { + chats := []dbmapping.Chat{} + + dc.squadsMutex.Lock() + for i := range dc.squadsWithChats { + for j := range squadsIDs { + if dc.squadsWithChats[i].Squad.ID == j { + chats = append(chats, dc.squadsWithChats[i].Chat) + } + } + } + dc.squadsMutex.Unlock() + + return chats +} + +// GetUserRolesInSquads returns all user roles for given user ID +func (dc *DataCache) GetUserRolesInSquads(userID int) []dbmapping.SquadPlayerFull { + userRoles := []dbmapping.SquadPlayerFull{} + + dc.squadsMutex.Lock() + for i := range dc.squadPlayers { + for j := range dc.squadPlayers[i] { + if dc.squadPlayers[i][j].Player.ID == userID { + userRoles = append(userRoles, *dc.squadPlayers[i][j]) + } + } + } + dc.squadsMutex.Unlock() + + return userRoles +} + +// GetUserRoleInSquad returns user role in specified squad +func (dc *DataCache) GetUserRoleInSquad(squadID int, playerID int) string { + if dc.squadPlayers[squadID][playerID] != nil { + return dc.squadPlayers[squadID][playerID].UserRole + } + + return "none" +} diff --git a/lib/dbmapping/squads.go b/lib/dbmapping/squads.go index e7facdb..bedb56f 100644 --- a/lib/dbmapping/squads.go +++ b/lib/dbmapping/squads.go @@ -9,16 +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"` - FloodChatID int `db:"flood_chat_id"` - AuthorID int `db:"author_id"` - CreatedAt time.Time `db:"created_at"` + ID int `db:"id"` + ChatID int `db:"chat_id"` + MinLevel int `db:"min_level"` + MaxLevel int `db:"max_level"` + InviteLink string `db:"invite_link"` + CreatedAt time.Time `db:"created_at"` } // SquadChat is a stuct, which combines information about chats and squads type SquadChat struct { - Squad Squad - Chat Chat - FloodChat Chat + Squad Squad + Chat Chat } diff --git a/lib/migrations/18_add_pokememes_wealth.go b/lib/migrations/18_add_pokememes_wealth.go index b867a92..82dff30 100644 --- a/lib/migrations/18_add_pokememes_wealth.go +++ b/lib/migrations/18_add_pokememes_wealth.go @@ -8,7 +8,7 @@ import ( "database/sql" ) -// AddPokememesWealth prepares database for latest game update in mid-October +// AddPokememesWealthUp prepares database for latest game update in mid-October func AddPokememesWealthUp(tx *sql.Tx) error { _, err := tx.Exec("ALTER TABLE `profiles` ADD COLUMN `pokememes_wealth` INT(11) NOT NULL DEFAULT 0 COMMENT 'Стоимость покемонов на руках' AFTER `wealth`;") if err != nil { diff --git a/lib/migrations/31_change_squads_table.go b/lib/migrations/31_change_squads_table.go new file mode 100644 index 0000000..051556a --- /dev/null +++ b/lib/migrations/31_change_squads_table.go @@ -0,0 +1,78 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package migrations + +import ( + "database/sql" +) + +// ChangeSquadsTableUp changes `sqauds` to new format +func ChangeSquadsTableUp(tx *sql.Tx) error { + request := "ALTER TABLE `squads` DROP COLUMN `flood_chat_id`" + _, err := tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` DROP COLUMN `author_id`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` ADD COLUMN `min_level` int(11) NOT NULL DEFAULT 0 AFTER `chat_id`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` ADD COLUMN `max_level` int(11) NOT NULL DEFAULT 0 AFTER `min_level`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` ADD COLUMN `invite_link` varchar(191) NOT NULL DEFAULT 'https://example.com' AFTER `max_level`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + return nil +} + +// ChangeSquadsTableDown reverts `squads` to old format +func ChangeSquadsTableDown(tx *sql.Tx) error { + request := "ALTER TABLE `squads` ADD COLUMN `flood_chat_id` int(11) NOT NULL AFTER `chat_id`" + _, err := tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` ADD COLUMN `author_id` int(11) NOT NULL AFTER `flood_chat_id" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` DROP COLUMN `min_level`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` DROP COLUMN `max_level`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + request = "ALTER TABLE `squads` DROP COLUMN `invite_link`" + _, err = tx.Exec(request) + if err != nil { + return err + } + + return nil +} diff --git a/lib/migrations/migrations.go b/lib/migrations/migrations.go index 2d63db4..64418f4 100644 --- a/lib/migrations/migrations.go +++ b/lib/migrations/migrations.go @@ -41,6 +41,7 @@ func (m *Migrations) Init() { goose.AddNamedMigration("28_fix_locations.go", FixLocationsUp, FixLocationsDown) goose.AddNamedMigration("29_fix_leagues_names.go", FixLeaguesNamesUp, FixLeaguesNamesDown) goose.AddNamedMigration("30_create_alarms.go", CreateAlarmsUp, CreateAlarmsUp) + goose.AddNamedMigration("31_change_squads_table.go", ChangeSquadsTableUp, ChangeSquadsTableDown) } // Migrate migrates database to current version diff --git a/lib/orders/orders.go b/lib/orders/orders.go index 52e2d71..6f8d5d4 100644 --- a/lib/orders/orders.go +++ b/lib/orders/orders.go @@ -26,13 +26,9 @@ func (o *Orders) getOrderByID(orderID int) (dbmapping.Order, bool) { func (o *Orders) sendOrder(order *dbmapping.Order) string { targetChats := []dbmapping.Chat{} - ok := false if order.TargetSquads == "all" { - targetChats, ok = c.Squader.GetAllSquadChats() - if !ok { - return "fail" - } + targetChats = c.DataCache.GetAllSquadsChats() // Adding Academy and Bastion chat as they are both the zero chat academyGroupID, _ := strconv.ParseInt(c.Cfg.SpecialChats.AcademyID, 10, 64) @@ -51,10 +47,13 @@ func (o *Orders) sendOrder(order *dbmapping.Order) string { targetChats = append(targetChats, academyChat) targetChats = append(targetChats, bastionChat) } else { - targetChats, ok = c.Squader.GetSquadChatsBySquadsIDs(order.TargetSquads) - if !ok { - return "fail" + targetIDs := make([]int, 0) + targetIDsArray := strings.Split(order.TargetSquads, ",") + for i := range targetIDsArray { + targetID, _ := strconv.Atoi(targetIDsArray[i]) + targetIDs = append(targetIDs, targetID) } + targetChats = c.DataCache.GetSquadsChatsBySquadsIDs(targetIDs) targetChatsIDs := strings.Split(order.TargetSquads, ",") for i := range targetChatsIDs { diff --git a/lib/pinner/pinner.go b/lib/pinner/pinner.go index b883393..fdee762 100644 --- a/lib/pinner/pinner.go +++ b/lib/pinner/pinner.go @@ -69,10 +69,7 @@ func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string { return "fail" } - groupChats, ok := c.Chatter.GetAllGroupChats() - if !ok { - return "fail" - } + groupChats := c.DataCache.GetAllGroupChats() return p.execMassMessagePin(update, groupChats) } @@ -100,10 +97,18 @@ func (p *Pinner) PinMessageToSomeChats(update *tgbotapi.Update) string { return "fail" } - groupChats, ok := c.Chatter.GetGroupChatsByIDs(chatsToPin) - if !ok { - return "fail" + chatsIDs := make([]int, 0) + chatsIDsArray := strings.Split(chatsToPin, ",") + for i := range chatsIDsArray { + chatIDInt, err := strconv.Atoi(chatsIDsArray[i]) + if err != nil { + c.Log.Error(err.Error()) + return "fail" + } + chatsIDs = append(chatsIDs, chatIDInt) } + + groupChats := c.DataCache.GetGroupChatsByIDs(chatsIDs) c.Log.Debug("Got " + strconv.Itoa(len(groupChats)) + " group chats...") return p.execMassMessagePin(update, groupChats) diff --git a/lib/router/private_request.go b/lib/router/private_request.go index 4d06a3a..b457d5a 100644 --- a/lib/router/private_request.go +++ b/lib/router/private_request.go @@ -4,9 +4,9 @@ package router import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" "regexp" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" ) func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmapping.Player, chatRaw *dbmapping.Chat) string { @@ -140,12 +140,7 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi return c.Talkers.AnyMessageUnauthorized(update) case update.Message.Command() == "squads": return c.Squader.SquadsList(update, playerRaw) - case update.Message.Command() == "make_squad": - if c.Users.PlayerBetterThan(playerRaw, "admin") { - return c.Squader.CreateSquad(update) - } - return c.Talkers.AnyMessageUnauthorized(update) case update.Message.Command() == "pin": if c.Users.PlayerBetterThan(playerRaw, "admin") { return c.Pinner.PinMessageToSomeChats(update) diff --git a/lib/router/router.go b/lib/router/router.go index fccacb5..22bcfac 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -16,15 +16,16 @@ func (r *Router) RouteRequest(update *tgbotapi.Update) string { return "fail" } - chatRaw, ok := c.Chatter.GetOrCreateChat(update) - if !ok { + chatRaw, err := c.DataCache.GetOrCreateChat(update) + if err != nil { + c.Log.Error(err.Error()) return "fail" } if update.Message.Chat.IsGroup() || update.Message.Chat.IsSuperGroup() { - return r.routeGroupRequest(update, playerRaw, &chatRaw) + return r.routeGroupRequest(update, playerRaw, chatRaw) } else if update.Message.Chat.IsPrivate() { - return r.routePrivateRequest(update, playerRaw, &chatRaw) + return r.routePrivateRequest(update, playerRaw, chatRaw) } return "ok" diff --git a/lib/squader/getters.go b/lib/squader/getters.go deleted file mode 100644 index ceb1f99..0000000 --- a/lib/squader/getters.go +++ /dev/null @@ -1,185 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package squader - -import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" - "strconv" - "strings" -) - -// GetCommandersForSquadViaChat gets commanders for selected chat -func (s *Squader) GetCommandersForSquadViaChat(chatRaw *dbmapping.Chat) ([]dbmapping.Player, bool) { - commanders := []dbmapping.Player{} - err := c.Db.Select(&commanders, c.Db.Rebind("SELECT p.* FROM players p, squads_players sp, squads s WHERE (s.chat_id=? OR s.flood_chat_id=?) AND sp.squad_id = s.id AND sp.user_type = 'commander' AND sp.player_id = p.id"), chatRaw.ID, chatRaw.ID) - if err != nil { - c.Log.Debug(err.Error()) - return commanders, false - } - - return commanders, true -} - -// GetSquadByID returns squad will all support information -func (s *Squader) GetSquadByID(squadID int) (dbmapping.SquadChat, bool) { - squadFull := dbmapping.SquadChat{} - squad := dbmapping.Squad{} - chat := dbmapping.Chat{} - floodChat := dbmapping.Chat{} - - err := c.Db.Get(&squad, c.Db.Rebind("SELECT * FROM squads WHERE id=?"), squadID) - if err != nil { - c.Log.Error(err) - return squadFull, false - } - - err = c.Db.Get(&chat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), squad.ChatID) - if err != nil { - c.Log.Error(err) - return squadFull, false - } - err = c.Db.Get(&floodChat, c.Db.Rebind("SELECT * FROM chats WHERE id=?"), squad.FloodChatID) - if err != nil { - c.Log.Error(err) - return squadFull, false - } - - squadFull.Squad = squad - squadFull.Chat = chat - squadFull.FloodChat = floodChat - - return squadFull, true -} - -// GetAvailableSquadChatsForUser returns squad chats which user can join -func (s *Squader) GetAvailableSquadChatsForUser(playerRaw *dbmapping.Player) ([]dbmapping.Chat, bool) { - groupChats := []dbmapping.Chat{} - - if playerRaw.LeagueID == 1 && playerRaw.Status != "spy" && playerRaw.Status != "league_changed" { - err := c.Db.Select(&groupChats, c.Db.Rebind("SELECT ch.* FROM chats ch, squads s, squads_players sp WHERE (s.chat_id=ch.id OR s.flood_chat_id=ch.id) AND sp.player_id = ? AND s.id = sp.squad_id"), playerRaw.ID) - if err != nil { - c.Log.Error(err) - return groupChats, false - } - } - - return groupChats, true -} - -// GetAllSquadChats returns all main squad chats -func (s *Squader) GetAllSquadChats() ([]dbmapping.Chat, bool) { - groupChats := []dbmapping.Chat{} - - err := c.Db.Select(&groupChats, "SELECT ch.* FROM chats ch, squads s WHERE s.chat_id=ch.id") - if err != nil { - c.Log.Error(err) - return groupChats, false - } - - return groupChats, true -} - -// GetAllSquadFloodChats returns all flood squad chats -func (s *Squader) GetAllSquadFloodChats() ([]dbmapping.Chat, bool) { - groupChats := []dbmapping.Chat{} - - err := c.Db.Select(&groupChats, "SELECT ch.* FROM chats ch, squads s WHERE s.flood_chat_id=ch.id") - if err != nil { - c.Log.Error(err) - return groupChats, false - } - - return groupChats, true -} - -// GetSquadChatsBySquadsIDs returns main squad chats for given squads IDs -func (s *Squader) GetSquadChatsBySquadsIDs(squadsIDs string) ([]dbmapping.Chat, bool) { - groupChats := []dbmapping.Chat{} - - squadsIDsArray := strings.Split(squadsIDs, ",") - if len(squadsIDsArray) < 1 { - return groupChats, false - } - - sIDs := make([]int, 0) - for i := range squadsIDsArray { - sID, _ := strconv.Atoi(squadsIDsArray[i]) - if sID != 0 { - sIDs = append(sIDs, sID) - } - } - if len(sIDs) < 1 { - return groupChats, false - } - - queryLine := "" - for i := range sIDs { - queryLine += strconv.Itoa(sIDs[i]) - if i < len(sIDs)-1 { - queryLine += "," - } - } - - err := c.Db.Select(&groupChats, "SELECT ch.* FROM chats ch, squads s WHERE s.chat_id=ch.id AND s.id IN ("+queryLine+")") - if err != nil { - c.Log.Error(err) - return groupChats, false - } - - return groupChats, true -} - -// GetUserRolesInSquads lists all user roles -func (s *Squader) GetUserRolesInSquads(playerRaw *dbmapping.Player) ([]dbmapping.SquadPlayerFull, bool) { - userRoles := []dbmapping.SquadPlayerFull{} - userRolesRaw := []dbmapping.SquadPlayer{} - - err := c.Db.Select(&userRolesRaw, c.Db.Rebind("SELECT * FROM squads_players WHERE player_id=?"), playerRaw.ID) - if err != nil { - c.Log.Error(err.Error()) - return userRoles, false - } - - for i := range userRolesRaw { - userRoleFull := dbmapping.SquadPlayerFull{} - userRoleFull.Player = *playerRaw - userProfile, profileError := c.DataCache.GetProfileByPlayerID(playerRaw.ID) - userRoleFull.Profile = *userProfile - userRoleFull.UserRole = userRolesRaw[i].UserType - squad, squadOk := s.GetSquadByID(userRolesRaw[i].SquadID) - userRoleFull.Squad = squad - - if profileError == nil && squadOk { - userRoles = append(userRoles, userRoleFull) - } - } - - return userRoles, true -} - -// IsChatASquadEnabled checks group chat for restricting actions for squad -func (s *Squader) IsChatASquadEnabled(chatRaw *dbmapping.Chat) string { - mainChats, ok := s.GetAllSquadChats() - if !ok { - return "no" - } - floodChats, ok := s.GetAllSquadFloodChats() - if !ok { - return "no" - } - - for i := range mainChats { - if *chatRaw == mainChats[i] { - return "main" - } - } - - for i := range floodChats { - if *chatRaw == floodChats[i] { - return "flood" - } - } - - return "no" -} diff --git a/lib/squader/responders.go b/lib/squader/responders.go index fdf73a9..0a85e80 100644 --- a/lib/squader/responders.go +++ b/lib/squader/responders.go @@ -17,10 +17,7 @@ func (s *Squader) SquadsList(update *tgbotapi.Update, playerRaw *dbmapping.Playe return c.Talkers.AnyMessageUnauthorized(update) } } - squads, ok := s.getAllSquadsWithChats() - if !ok { - return "fail" - } + squads := c.DataCache.GetAllSquadsWithChats() message := "*Наши отряды:*\n" message += "---\n" @@ -33,7 +30,6 @@ func (s *Squader) SquadsList(update *tgbotapi.Update, playerRaw *dbmapping.Playe 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 += c.Statistics.SquadStatictics(squads[i].Squad.ID) } @@ -55,13 +51,14 @@ func (s *Squader) SquadInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player } if !c.Users.PlayerBetterThan(playerRaw, "admin") { - if s.getUserRoleForSquad(squadID, playerRaw.ID) != "commander" { + if c.DataCache.GetUserRoleInSquad(squadID, playerRaw.ID) != "commander" { return c.Talkers.AnyMessageUnauthorized(update) } } - squad, ok := s.GetSquadByID(squadID) - if !ok { + squad, err := c.DataCache.GetSquadByID(squadID) + if err != nil { + c.Log.Error(err.Error()) return c.Talkers.BotError(update) } diff --git a/lib/squader/restricters.go b/lib/squader/restricters.go deleted file mode 100644 index da2b466..0000000 --- a/lib/squader/restricters.go +++ /dev/null @@ -1,28 +0,0 @@ -// i2_bot – Instinct PokememBro Bot -// Copyright (c) 2017 Vladimir "fat0troll" Hodakov - -package squader - -import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" - "github.com/go-telegram-bot-api/telegram-bot-api" -) - -// CleanFlood will clean flood from squads -func (s *Squader) CleanFlood(update *tgbotapi.Update, chatRaw *dbmapping.Chat) string { - switch s.IsChatASquadEnabled(chatRaw) { - case "main": - talker, err := c.DataCache.GetPlayerByTelegramID(update.Message.From.ID) - if err != nil { - c.Log.Error(err.Error()) - s.deleteFloodMessage(update) - return "fail" - } - if (update.Message.From.UserName != "i2_bot") && (update.Message.From.UserName != "i2_bot_dev") && !s.isUserAnyCommander(talker.ID) { - s.deleteFloodMessage(update) - return "fail" - } - } - - return "protection_passed" -} diff --git a/lib/squader/squader.go b/lib/squader/squader.go index aa075ba..f16de36 100644 --- a/lib/squader/squader.go +++ b/lib/squader/squader.go @@ -4,9 +4,9 @@ package squader import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" "regexp" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "strconv" "strings" "time" @@ -17,12 +17,13 @@ func (s *Squader) getPlayersForSquad(squadID int) ([]dbmapping.SquadPlayerFull, playersRaw := []dbmapping.Player{} squadPlayers := []dbmapping.SquadPlayer{} - squad, ok := s.GetSquadByID(squadID) - if !ok { + squad, err := c.DataCache.GetSquadByID(squadID) + if err != nil { + c.Log.Error(err.Error()) return players, false } - err := c.Db.Select(&playersRaw, c.Db.Rebind("SELECT p.* FROM players p, squads_players sp WHERE p.id = sp.player_id AND sp.squad_id=?"), squad.Squad.ID) + err = c.Db.Select(&playersRaw, c.Db.Rebind("SELECT p.* FROM players p, squads_players sp WHERE p.id = sp.player_id AND sp.squad_id=?"), squad.Squad.ID) if err != nil { c.Log.Error(err.Error()) return players, false @@ -38,13 +39,17 @@ func (s *Squader) getPlayersForSquad(squadID int) ([]dbmapping.SquadPlayerFull, for ii := range squadPlayers { if squadPlayers[ii].PlayerID == playersRaw[i].ID { playerWithProfile := dbmapping.SquadPlayerFull{} - profile, _ := c.DataCache.GetProfileByPlayerID(playersRaw[i].ID) - playerWithProfile.Profile = *profile - playerWithProfile.Player = playersRaw[i] - playerWithProfile.Squad = squad - playerWithProfile.UserRole = squadPlayers[ii].UserType + profile, err := c.DataCache.GetProfileByPlayerID(playersRaw[i].ID) + if err != nil { + c.Log.Error(err.Error()) + } else { + playerWithProfile.Profile = *profile + playerWithProfile.Player = playersRaw[i] + playerWithProfile.Squad = *squad + playerWithProfile.UserRole = squadPlayers[ii].UserType - players = append(players, playerWithProfile) + players = append(players, playerWithProfile) + } } } } @@ -52,140 +57,6 @@ func (s *Squader) getPlayersForSquad(squadID int) ([]dbmapping.SquadPlayerFull, return players, true } -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, err := c.DataCache.GetPlayerByTelegramID(update.Message.From.ID) - if err != nil { - c.Log.Error(err.Error()) - 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) getUserRoleForSquad(squadID int, playerID int) string { - squadPlayer := dbmapping.SquadPlayer{} - err := c.Db.Get(&squadPlayer, c.Db.Rebind("SELECT * FROM squads_players WHERE squad_id=? AND player_id=?"), squadID, playerID) - if err != nil { - c.Log.Debug(err.Error()) - return "nobody" - } - - return squadPlayer.UserType -} - -func (s *Squader) deleteFloodMessage(update *tgbotapi.Update) { - deleteMessageConfig := tgbotapi.DeleteMessageConfig{ - ChatID: update.Message.Chat.ID, - MessageID: update.Message.MessageID, - } - - _, err := c.Bot.DeleteMessage(deleteMessageConfig) - if err != nil { - c.Log.Error(err.Error()) - } -} - func (s *Squader) isUserAnyCommander(playerID int) bool { squadPlayers := []dbmapping.SquadPlayer{} err := c.Db.Select(&squadPlayers, c.Db.Rebind("SELECT * FROM squads_players WHERE player_id=? AND user_type='commander'"), playerID) @@ -200,42 +71,6 @@ func (s *Squader) isUserAnyCommander(playerID int) bool { return false } -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" -} - func (s *Squader) squadUserAdditionFailure(update *tgbotapi.Update) string { message := "*Не удалось добавить игрока в отряд*\n" message += "Проверьте, правильно ли вы ввели команду, и повторите попытку. Кроме того, возможно, что у пользователя нет профиля в боте." @@ -293,8 +128,12 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) } - squadRaw := dbmapping.Squad{} - err = c.Db.Get(&squadRaw, c.Db.Rebind("SELECT * FROM squads WHERE id=?"), squadID) + profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID) + if err != nil { + c.Log.Error(err.Error()) + return s.squadUserAdditionFailure(update) + } + squadRaw, err := c.DataCache.GetSquadByID(squadID) if err != nil { c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) @@ -313,7 +152,17 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl return c.Talkers.AnyMessageUnauthorized(update) } - if s.getUserRoleForSquad(squadRaw.ID, adderRaw.ID) != "commander" { + userRoles := c.DataCache.GetUserRolesInSquads(adderRaw.ID) + isCommander := false + for i := range userRoles { + if userRoles[i].UserRole == "commander" { + if userRoles[i].Squad.Squad.ID == squadRaw.Squad.ID { + isCommander = true + } + } + } + + if !isCommander { return c.Talkers.AnyMessageUnauthorized(update) } } @@ -321,18 +170,22 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl if !c.Users.PlayerBetterThan(playerRaw, "admin") { if playerRaw.LeagueID != 1 { return s.squadUserAdditionFailure(update) + } else if squadRaw.Squad.MinLevel > profileRaw.LevelID { + return s.squadUserAdditionFailure(update) + } else if squadRaw.Squad.MaxLevel-1 < profileRaw.LevelID { + return s.squadUserAdditionFailure(update) } } // All checks are passed here, creating new item in database playerSquad := dbmapping.SquadPlayer{} - playerSquad.SquadID = squadRaw.ID + playerSquad.SquadID = squadRaw.Squad.ID playerSquad.PlayerID = playerRaw.ID playerSquad.UserType = userType playerSquad.AuthorID = adderRaw.ID playerSquad.CreatedAt = time.Now().UTC() - _, err = c.Db.NamedExec("INSERT INTO squads_players VALUES(NULL, :squad_id, :player_id, :user_type, :author_id, :created_at)", &playerSquad) + _, err = c.DataCache.AddPlayerToSquad(&playerSquad) if err != nil { c.Log.Error(err.Error()) return s.squadUserAdditionFailure(update) @@ -340,35 +193,3 @@ func (s *Squader) AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Pl return s.squadUserAdditionSuccess(update) } - -// 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) -} diff --git a/lib/squader/squaderinterface/squaderinterface.go b/lib/squader/squaderinterface/squaderinterface.go index e9d316d..281a633 100644 --- a/lib/squader/squaderinterface/squaderinterface.go +++ b/lib/squader/squaderinterface/squaderinterface.go @@ -12,20 +12,8 @@ import ( type SquaderInterface interface { Init() - GetAllSquadChats() ([]dbmapping.Chat, bool) - GetAllSquadFloodChats() ([]dbmapping.Chat, bool) - GetAvailableSquadChatsForUser(playerRaw *dbmapping.Player) ([]dbmapping.Chat, bool) - GetCommandersForSquadViaChat(chatRaw *dbmapping.Chat) ([]dbmapping.Player, bool) - GetSquadByID(squadID int) (dbmapping.SquadChat, bool) - GetSquadChatsBySquadsIDs(squadsID string) ([]dbmapping.Chat, bool) - GetUserRolesInSquads(playerRaw *dbmapping.Player) ([]dbmapping.SquadPlayerFull, bool) - IsChatASquadEnabled(chatRaw *dbmapping.Chat) string - AddUserToSquad(update *tgbotapi.Update, adderRaw *dbmapping.Player) string - CreateSquad(update *tgbotapi.Update) string SquadInfo(update *tgbotapi.Update, playerRaw *dbmapping.Player) string SquadsList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string - - CleanFlood(update *tgbotapi.Update, chatRaw *dbmapping.Chat) string } diff --git a/lib/statistics/squads.go b/lib/statistics/squads.go index d8c67cf..dd21fde 100644 --- a/lib/statistics/squads.go +++ b/lib/statistics/squads.go @@ -13,12 +13,13 @@ func (s *Statistics) SquadStatictics(squadID int) string { squadMembersWithInformation := []dbmapping.SquadPlayerFull{} squadMembers := []dbmapping.SquadPlayer{} - squad, ok := c.Squader.GetSquadByID(squadID) - if !ok { + squad, err := c.DataCache.GetSquadByID(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) + 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 "Невозможно получить информацию о данном отряде. Возможно, он пуст или произошла ошибка." @@ -38,7 +39,7 @@ func (s *Statistics) SquadStatictics(squadID int) string { continue } - fullInfo.Squad = squad + fullInfo.Squad = *squad fullInfo.Player = *playerRaw fullInfo.Profile = *profileRaw diff --git a/lib/users/responders.go b/lib/users/responders.go index 248256e..84ca048 100644 --- a/lib/users/responders.go +++ b/lib/users/responders.go @@ -4,8 +4,8 @@ package users import ( - "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "github.com/go-telegram-bot-api/telegram-bot-api" + "source.wtfteam.pro/i2_bot/i2_bot/lib/dbmapping" "strconv" "strings" ) @@ -194,8 +194,8 @@ func (u *Users) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Pla message += "\n\nСтатус в боте: _игрок_" } - squadRoles, ok := c.Squader.GetUserRolesInSquads(playerRaw) - if ok && len(squadRoles) > 0 { + squadRoles := c.DataCache.GetUserRolesInSquads(playerRaw.ID) + if len(squadRoles) > 0 { for i := range squadRoles { if squadRoles[i].UserRole == "commander" { message += "\nКомандир отряда " + squadRoles[i].Squad.Chat.Name diff --git a/lib/welcomer/notifications.go b/lib/welcomer/notifications.go index ba15e2a..ea82bd2 100644 --- a/lib/welcomer/notifications.go +++ b/lib/welcomer/notifications.go @@ -10,8 +10,9 @@ import ( func (w *Welcomer) alertUserWithoutProfile(update *tgbotapi.Update, newUser *tgbotapi.User) string { alertGroupID, _ := strconv.ParseInt(c.Cfg.SpecialChats.HeadquartersID, 10, 64) - chat, ok := c.Chatter.GetOrCreateChat(update) - if !ok { + chat, err := c.DataCache.GetOrCreateChat(update) + if err != nil { + c.Log.Error(err.Error()) return "fail" } @@ -39,8 +40,9 @@ 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.SpecialChats.HeadquartersID, 10, 64) - chat, ok := c.Chatter.GetOrCreateChat(update) - if !ok { + chat, err := c.DataCache.GetOrCreateChat(update) + if err != nil { + c.Log.Error(err.Error()) return "fail" }