diff --git a/i2_bot.go b/cmd/i2_bot/i2_bot.go similarity index 100% rename from i2_bot.go rename to cmd/i2_bot/i2_bot.go diff --git a/lib/dbmapping/chats.go b/lib/dbmapping/chats.go index 41e82d1..62c3381 100644 --- a/lib/dbmapping/chats.go +++ b/lib/dbmapping/chats.go @@ -13,6 +13,6 @@ type Chat struct { ID int `db:"id"` Name string `db:"name"` ChatType string `db:"chat_type"` - TelegramID int `db:"telegram_id"` + TelegramID int64 `db:"telegram_id"` CreatedAt time.Time `db:"created_at"` } diff --git a/lib/getters/chat.go b/lib/getters/chat.go index 97e24e5..cdab57a 100644 --- a/lib/getters/chat.go +++ b/lib/getters/chat.go @@ -14,7 +14,7 @@ import ( ) // GetChatByID returns dbmapping.Chat instance with given ID. -func (g *Getters) GetChatByID(chatID int) (dbmapping.Chat, bool) { +func (g *Getters) 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 { @@ -29,14 +29,30 @@ func (g *Getters) GetChatByID(chatID int) (dbmapping.Chat, bool) { // 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) { chatRaw := dbmapping.Chat{} + log.Println("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 { log.Printf("Chat stream not found in database.") log.Printf(err.Error()) - chatRaw.Name = telegramUpdate.Message.Chat.FirstName + " " + telegramUpdate.Message.Chat.LastName + 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 = int(telegramUpdate.Message.Chat.ID) + 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 { @@ -67,3 +83,15 @@ func (g *Getters) GetAllPrivateChats() ([]dbmapping.Chat, bool) { return privateChats, 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 { + log.Println(err) + return chatRaw, false + } + + return chatRaw, true +} diff --git a/lib/getters/gettersinterface/gettersinterface.go b/lib/getters/gettersinterface/gettersinterface.go index 00d222c..ede0a8b 100644 --- a/lib/getters/gettersinterface/gettersinterface.go +++ b/lib/getters/gettersinterface/gettersinterface.go @@ -17,8 +17,9 @@ type GettersInterface interface { GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) UpdateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) GetOrCreateChat(update *tgbotapi.Update) (dbmapping.Chat, bool) - GetChatByID(chatID int) (dbmapping.Chat, bool) + GetChatByID(chatID int64) (dbmapping.Chat, bool) GetAllPrivateChats() ([]dbmapping.Chat, 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/router/group_request.go b/lib/router/group_request.go new file mode 100644 index 0000000..6d2f21c --- /dev/null +++ b/lib/router/group_request.go @@ -0,0 +1,52 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package router + +import ( + // stdlib + "regexp" + // 3rd party + "github.com/go-telegram-bot-api/telegram-bot-api" + // local + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +func (r *Router) routeGroupRequest(update tgbotapi.Update, playerRaw dbmapping.Player, chatRaw dbmapping.Chat) string { + text := update.Message.Text + // Regular expressions + var durakMsg = regexp.MustCompile("(Д|д)(У|у)(Р|р)(А|а|Е|е|О|о)") + var huMsg = regexp.MustCompile("(Х|х)(У|у)(Й|й|Я|я|Ю|ю|Е|е)") + var blMsg = regexp.MustCompile("(\\s|^)(Б|б)(Л|л)(Я|я)(Т|т|Д|д)") + var ebMsg = regexp.MustCompile("(\\s|^|ЗА|За|зА|за)(Е|е|Ё|ё)(Б|б)(\\s|Л|л|А|а|Т|т|У|у|Е|е|Ё|ё|И|и)") + var piMsg = regexp.MustCompile("(П|п)(И|и)(З|з)(Д|д)") + + // Welcomes + if update.Message.NewChatMember != nil { + return c.Talkers.WelcomeMessage(update) + } + // New chat names + if update.Message.NewChatTitle != "" { + _, ok := c.Getters.UpdateChatTitle(chatRaw, update.Message.NewChatTitle) + if ok { + return "ok" + } + + return "fail" + } + + switch { + case huMsg.MatchString(text): + c.Talkers.MatMessage(update) + case blMsg.MatchString(text): + c.Talkers.MatMessage(update) + case ebMsg.MatchString(text): + c.Talkers.MatMessage(update) + case piMsg.MatchString(text): + c.Talkers.MatMessage(update) + case durakMsg.MatchString(text): + c.Talkers.DurakMessage(update) + } + + return "ok" +} diff --git a/lib/router/private_request.go b/lib/router/private_request.go new file mode 100644 index 0000000..3076efc --- /dev/null +++ b/lib/router/private_request.go @@ -0,0 +1,143 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package router + +import ( + // stdlib + "log" + "regexp" + "strings" + // 3rd party + "github.com/go-telegram-bot-api/telegram-bot-api" + // local + "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +func (r *Router) routePrivateRequest(update tgbotapi.Update, playerRaw dbmapping.Player, chatRaw dbmapping.Chat) string { + text := update.Message.Text + // Forwards + var pokememeMsg = regexp.MustCompile("(Уровень)(.+)(Опыт)(.+)\n(Элементы:)(.+)\n(.+)(💙MP)") + var profileMsg = regexp.MustCompile(`(Онлайн: )(\d+)\n(Турнир через)(.+)\n\n((.*)\n|(.*)\n(.*)\n)(Элементы)(.+)\n(.*)\n\n(.+)(Уровень)(.+)\n`) + + // Commands with regexps + var pokedexMsg = regexp.MustCompile("/pokede(x|ks)\\d?\\z") + var pokememeInfoMsg = regexp.MustCompile("/pk(\\d+)") + + if update.Message.ForwardFrom != nil { + if update.Message.ForwardFrom.ID != 360402625 { + log.Printf("Forward from another user or bot. Ignoring") + } else { + log.Printf("Forward from PokememBro bot! Processing...") + if playerRaw.ID != 0 { + switch { + case pokememeMsg.MatchString(text): + log.Printf("Pokememe posted!") + if playerRaw.LeagueID == 1 { + status := c.Parsers.ParsePokememe(text, playerRaw) + switch status { + case "ok": + c.Talkers.PokememeAddSuccessMessage(update) + return "ok" + case "dup": + c.Talkers.PokememeAddDuplicateMessage(update) + return "ok" + case "fail": + c.Talkers.PokememeAddFailureMessage(update) + return "fail" + } + } else { + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + } + case profileMsg.MatchString(text): + log.Printf("Profile posted!") + status := c.Parsers.ParseProfile(update, playerRaw) + switch status { + case "ok": + c.Talkers.ProfileAddSuccessMessage(update) + return "ok" + case "fail": + c.Talkers.ProfileAddFailureMessage(update) + return "fail" + } + default: + log.Printf(text) + return "fail" + } + } else { + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + } + } + } else { + if update.Message.IsCommand() { + switch { + case update.Message.Command() == "start": + if playerRaw.ID != 0 { + c.Talkers.HelloMessageAuthorized(update, playerRaw) + return "ok" + } + + c.Talkers.HelloMessageUnauthorized(update) + return "ok" + case update.Message.Command() == "help": + c.Talkers.HelpMessage(update, &playerRaw) + return "ok" + // Pokememes info + case pokedexMsg.MatchString(text): + if strings.HasSuffix(text, "1") { + c.Talkers.PokememesList(update, 1) + return "ok" + } else if strings.HasSuffix(text, "2") { + c.Talkers.PokememesList(update, 2) + return "ok" + } else if strings.HasSuffix(text, "3") { + c.Talkers.PokememesList(update, 3) + return "ok" + } else if strings.HasSuffix(text, "4") { + c.Talkers.PokememesList(update, 4) + return "ok" + } else if strings.HasSuffix(text, "5") { + c.Talkers.PokememesList(update, 5) + return "ok" + } + + c.Talkers.PokememesList(update, 1) + return "ok" + case pokememeInfoMsg.MatchString(text): + c.Talkers.PokememeInfo(update, playerRaw) + return "ok" + case update.Message.Command() == "me": + if playerRaw.ID != 0 { + c.Talkers.ProfileMessage(update, playerRaw) + return "ok" + } + + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + case update.Message.Command() == "best": + c.Talkers.BestPokememesList(update, playerRaw) + return "ok" + case update.Message.Command() == "send_all": + if c.Getters.PlayerBetterThan(&playerRaw, "admin") { + c.Talkers.AdminBroadcastMessageCompose(update, &playerRaw) + return "ok" + } + + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + case update.Message.Command() == "send_confirm": + if c.Getters.PlayerBetterThan(&playerRaw, "admin") { + c.Talkers.AdminBroadcastMessageSend(update, &playerRaw) + return "ok" + } + + c.Talkers.AnyMessageUnauthorized(update) + return "fail" + } + } + } + + return "fail" +} diff --git a/lib/router/router.go b/lib/router/router.go index 86fd137..0973eef 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -6,8 +6,6 @@ package router import ( // stdlib "log" - "regexp" - "strings" // 3rd party "github.com/go-telegram-bot-api/telegram-bot-api" ) @@ -17,8 +15,6 @@ type Router struct{} // RouteRequest decides, what to do with user input func (r *Router) RouteRequest(update tgbotapi.Update) string { - text := update.Message.Text - playerRaw, ok := c.Getters.GetOrCreatePlayer(update.Message.From.ID) if !ok { // Silently fail @@ -33,130 +29,10 @@ func (r *Router) RouteRequest(update tgbotapi.Update) string { log.Printf("Received message from chat ") log.Println(chatRaw.TelegramID) - // Regular expressions - var durakMsg = regexp.MustCompile("(Д|д)(У|у)(Р|р)(А|а|Е|е|О|о)") - var huMsg = regexp.MustCompile("(Х|х)(У|у)(Й|й|Я|я|Ю|ю|Е|е)") - var blMsg = regexp.MustCompile("(\\s|^)(Б|б)(Л|л)(Я|я)(Т|т|Д|д)") - var ebMsg = regexp.MustCompile("(\\s|^|ЗА|За|зА|за)(Е|е|Ё|ё)(Б|б)(\\s|Л|л|А|а|Т|т|У|у|Е|е|Ё|ё|И|и)") - var piMsg = regexp.MustCompile("(П|п)(И|и)(З|з)(Д|д)") - - // Commands - var helpMsg = regexp.MustCompile("/help\\z") - var helloMsg = regexp.MustCompile("/start\\z") - var pokedexMsg = regexp.MustCompile("/pokede(x|ks)\\d?\\z") - var pokememeInfoMsg = regexp.MustCompile("/pk(\\d+)") - var meMsg = regexp.MustCompile("/me\\z") - var bestMsg = regexp.MustCompile("/best\\z") - - // Owner commands - var sendAllMsg = regexp.MustCompile("/send_all(.+)") - var sendConfirmMsg = regexp.MustCompile(`/send_confirm(\s)(\d+)`) - - // Forwards - var pokememeMsg = regexp.MustCompile("(Уровень)(.+)(Опыт)(.+)\n(Элементы:)(.+)\n(.+)(💙MP)") - var profileMsg = regexp.MustCompile(`(Онлайн: )(\d+)\n(Турнир через)(.+)\n\n(.*)\n(Элементы)(.+)\n(.*)\n\n(.+)(Уровень)(.+)\n`) - - if update.Message.ForwardFrom != nil { - if update.Message.ForwardFrom.ID != 360402625 { - log.Printf("Forward from another user or bot. Ignoring") - } else { - log.Printf("Forward from PokememBro bot! Processing...") - if playerRaw.ID != 0 { - switch { - case pokememeMsg.MatchString(text): - log.Printf("Pokememe posted!") - status := c.Parsers.ParsePokememe(text, playerRaw) - switch status { - case "ok": - c.Talkers.PokememeAddSuccessMessage(update) - case "dup": - c.Talkers.PokememeAddDuplicateMessage(update) - case "fail": - c.Talkers.PokememeAddFailureMessage(update) - } - case profileMsg.MatchString(text): - log.Printf("Profile posted!") - status := c.Parsers.ParseProfile(update, playerRaw) - switch status { - case "ok": - c.Talkers.ProfileAddSuccessMessage(update) - case "fail": - c.Talkers.ProfileAddFailureMessage(update) - } - default: - log.Printf(text) - } - } else { - c.Talkers.AnyMessageUnauthorized(update) - } - } - } else { - // Direct messages from user - switch { - case helloMsg.MatchString(text): - if playerRaw.ID != 0 { - c.Talkers.HelloMessageAuthorized(update, playerRaw) - } else { - c.Talkers.HelloMessageUnauthorized(update) - } - // Help - case helpMsg.MatchString(text): - c.Talkers.HelpMessage(update, &playerRaw) - // Pokememes info - case pokedexMsg.MatchString(text): - if strings.HasSuffix(text, "1") { - c.Talkers.PokememesList(update, 1) - } else if strings.HasSuffix(text, "2") { - c.Talkers.PokememesList(update, 2) - } else if strings.HasSuffix(text, "3") { - c.Talkers.PokememesList(update, 3) - } else if strings.HasSuffix(text, "4") { - c.Talkers.PokememesList(update, 4) - } else if strings.HasSuffix(text, "5") { - c.Talkers.PokememesList(update, 5) - } else { - c.Talkers.PokememesList(update, 1) - } - case pokememeInfoMsg.MatchString(text): - c.Talkers.PokememeInfo(update, playerRaw) - // Profile info - case meMsg.MatchString(text): - if playerRaw.ID != 0 { - c.Talkers.ProfileMessage(update, playerRaw) - } else { - c.Talkers.AnyMessageUnauthorized(update) - } - // Suggestions - case bestMsg.MatchString(text): - c.Talkers.BestPokememesList(update, playerRaw) - // Admin commands - case sendAllMsg.MatchString(text): - if c.Getters.PlayerBetterThan(&playerRaw, "admin") { - c.Talkers.AdminBroadcastMessageCompose(update, &playerRaw) - } else { - c.Talkers.AnyMessageUnauthorized(update) - } - case sendConfirmMsg.MatchString(text): - if c.Getters.PlayerBetterThan(&playerRaw, "admin") { - c.Talkers.AdminBroadcastMessageSend(update, &playerRaw) - } else { - c.Talkers.AnyMessageUnauthorized(update) - } - // Easter eggs - case huMsg.MatchString(text): - c.Talkers.MatMessage(update) - case blMsg.MatchString(text): - c.Talkers.MatMessage(update) - case ebMsg.MatchString(text): - c.Talkers.MatMessage(update) - case piMsg.MatchString(text): - c.Talkers.MatMessage(update) - case durakMsg.MatchString(text): - c.Talkers.DurakMessage(update) - default: - log.Printf("User posted unknown command.") - return "fail" - } + if update.Message.Chat.IsGroup() || update.Message.Chat.IsSuperGroup() { + return r.routeGroupRequest(update, playerRaw, chatRaw) + } else if update.Message.Chat.IsPrivate() { + return r.routePrivateRequest(update, playerRaw, chatRaw) } return "ok" diff --git a/lib/talkers/easter.go b/lib/talkers/easter.go index a15bdfb..c257abe 100644 --- a/lib/talkers/easter.go +++ b/lib/talkers/easter.go @@ -20,7 +20,6 @@ func (t *Talkers) DurakMessage(update tgbotapi.Update) { reactions = append(reactions, "Сам такой!", "А ты типа нет?", "Фу, как некультурно!", - "Профессор, если вы такой умный, то почему вы такой бедный? /donate", "Попка – не дурак, Попка – самый непадающий бот!") // Praise the Random Gods! diff --git a/lib/talkers/talkersinterface/talkersinterface.go b/lib/talkers/talkersinterface/talkersinterface.go index 1c969b9..b8759b1 100644 --- a/lib/talkers/talkersinterface/talkersinterface.go +++ b/lib/talkers/talkersinterface/talkersinterface.go @@ -31,7 +31,9 @@ type TalkersInterface interface { GetterError(update tgbotapi.Update) AdminBroadcastMessageCompose(update tgbotapi.Update, playerRaw *dbmapping.Player) string - AdminBroadcastMessageSend(update tgbotapi.Update, playerRaw *dbmapping.Player) string + AdminBroadcastMessageSend(update tgbotapi.Update, playerRaw *dbmapping.Player) string + + WelcomeMessage(update tgbotapi.Update) string DurakMessage(update tgbotapi.Update) MatMessage(update tgbotapi.Update) diff --git a/lib/talkers/welcome.go b/lib/talkers/welcome.go new file mode 100644 index 0000000..043112c --- /dev/null +++ b/lib/talkers/welcome.go @@ -0,0 +1,68 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package talkers + +import ( + // stdlib + "time" + // 3rd party + "github.com/go-telegram-bot-api/telegram-bot-api" + // local + // "lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping" +) + +func (t *Talkers) groupWelcomeUser(update tgbotapi.Update) string { + playerRaw, ok := c.Getters.GetOrCreatePlayer(update.Message.NewChatMember.ID) + if !ok { + return "fail" + } + + profileRaw, profileExist := c.Getters.GetProfile(playerRaw.ID) + + message := "*Бот Инстинкта приветствует тебя, *@" + message += update.Message.NewChatMember.UserName + message += "*!*\n\n" + + if profileExist { + if playerRaw.LeagueID == 1 { + message += "Рад тебя видеть! Не забывай обновлять профиль почаще, и да пребудет с тобой Рандом!\n" + message += "Последнее обновление твоего профиля: " + profileRaw.CreatedAt.Format("02.01.2006 15:04:05") + "." + } else { + message += "Обнови профиль, отправив его боту в личку. Так надо." + } + } else { + // newbie + message += "Добавь себе бота @i2\\_bot в список контактов и скинь в него игровой профиль. Это важно для успешной игры!\n" + } + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} + +func (t *Talkers) groupStartMessage(update tgbotapi.Update) string { + message := "*Бот Инстинкта приветствует этот чатик!*\n\n" + message += "На слубже здравого смысла с " + time.Now().Format("02.01.2006 15:04:05") + "." + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} + +// WelcomeMessage welcomes new user on group or bot itself +func (t *Talkers) WelcomeMessage(update tgbotapi.Update) string { + if (update.Message.NewChatMember.UserName == "i2_bot") || (update.Message.NewChatMember.UserName == "i2_dev_bot") { + return t.groupStartMessage(update) + } else { + return t.groupWelcomeUser(update) + } + + return "fail" +}