diff --git a/lib/dbmapping/broadcast.go b/lib/dbmapping/broadcast.go new file mode 100644 index 0000000..5d912e1 --- /dev/null +++ b/lib/dbmapping/broadcast.go @@ -0,0 +1,19 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package dbmapping + +import ( + // stdlib + "time" +) + +// Broadcast is a struct, which represents `broadcast` table item in databse. +type Broadcast struct { + ID int `db:"id"` + Text string `db:"text"` + BroadcastType string `db:"broadcast_type"` + Status string `db:"status"` + AuthorID int `db:"author_id"` + CreatedAt time.Time `db:"created_at"` +} diff --git a/lib/getters/broadcasts.go b/lib/getters/broadcasts.go new file mode 100644 index 0000000..fd6f673 --- /dev/null +++ b/lib/getters/broadcasts.go @@ -0,0 +1,69 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package getters + +import ( + // stdlib + "log" + "time" + // local + "../dbmapping" +) + +// CreateBroadcastMessage creates broadcast message item in database +func (g *Getters) CreateBroadcastMessage(playerRaw *dbmapping.Player, messageBody string, broadcastType string) (dbmapping.Broadcast, bool) { + messageRaw := dbmapping.Broadcast{} + messageRaw.Text = messageBody + messageRaw.Status = "new" + messageRaw.BroadcastType = broadcastType + messageRaw.AuthorID = playerRaw.ID + messageRaw.CreatedAt = time.Now().UTC() + _, err := c.Db.NamedExec("INSERT INTO broadcasts VALUES(NULL, :text, :broadcast_type, :status, :author_id, :created_at)", &messageRaw) + if err != nil { + log.Printf(err.Error()) + return messageRaw, false + } + err2 := c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE author_id=? AND text=?"), messageRaw.AuthorID, messageRaw.Text) + if err2 != nil { + log.Println(err2) + return messageRaw, false + } + + return messageRaw, true +} + +// GetBroadcastMessageByID returns dbmapping.Broadcast instance with given ID. +func (g *Getters) GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) { + messageRaw := dbmapping.Broadcast{} + err := c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE id=?"), messageID) + if err != nil { + log.Println(err) + return messageRaw, false + } + + return messageRaw, true +} + +// UpdateBroadcastMessageStatus updates broadcast message status +func (g *Getters) UpdateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) { + messageRaw := dbmapping.Broadcast{} + err := c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE id=?"), messageID) + if err != nil { + log.Println(err) + return messageRaw, false + } + messageRaw.Status = messageStatus + _, err = c.Db.NamedExec("UPDATE broadcasts SET status=:status WHERE id=:id", &messageRaw) + if err != nil { + log.Printf(err.Error()) + return messageRaw, false + } + err = c.Db.Get(&messageRaw, c.Db.Rebind("SELECT * FROM broadcasts WHERE author_id=? AND text=?"), messageRaw.AuthorID, messageRaw.Text) + if err != nil { + log.Println(err) + return messageRaw, false + } + + return messageRaw, true +} diff --git a/lib/getters/gettersinterface/gettersinterface.go b/lib/getters/gettersinterface/gettersinterface.go index 34a597b..481cb05 100644 --- a/lib/getters/gettersinterface/gettersinterface.go +++ b/lib/getters/gettersinterface/gettersinterface.go @@ -13,6 +13,9 @@ import ( // GettersInterface implements Getters for importing via appcontext. type GettersInterface interface { Init() + CreateBroadcastMessage(playerRaw *dbmapping.Player, messageBody string, broadcastType string) (dbmapping.Broadcast, bool) + GetBroadcastMessageByID(messageID int) (dbmapping.Broadcast, bool) + UpdateBroadcastMessageStatus(messageID int, messageStatus string) (dbmapping.Broadcast, bool) GetOrCreateChat(update *tgbotapi.Update) (dbmapping.Chat, bool) GetChatByID(chatID int) (dbmapping.Chat, bool) GetAllPrivateChats() ([]dbmapping.Chat, bool) diff --git a/lib/migrations/19_create_broadcasts.go b/lib/migrations/19_create_broadcasts.go new file mode 100644 index 0000000..f707517 --- /dev/null +++ b/lib/migrations/19_create_broadcasts.go @@ -0,0 +1,37 @@ +// i2_bot – Instinct PokememBro Bot +// Copyright (c) 2017 Vladimir "fat0troll" Hodakov + +package migrations + +import ( + // stdlib + "database/sql" +) + +func CreateBroadcastsUp(tx *sql.Tx) error { + request := "CREATE TABLE `broadcasts` (" + request += "`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID сообщения'," + request += "`text` text NOT NULL COMMENT 'Тело сообщения'," + request += "`broadcast_type` varchar(191) NOT NULL COMMENT 'Тип широковещательного сообщения'," + request += "`status` varchar(191) NOT NULL DEFAULT 'new' COMMENT 'Статус сообщения'," + request += "`author_id` int(11) NOT NULL COMMENT 'ID автора'," + request += "`created_at` datetime NOT NULL COMMENT 'Добавлено в базу'," + request += "PRIMARY KEY (`id`)," + request += "UNIQUE KEY `id` (`id`)," + request += "KEY `broadcasts_created_at` (`created_at`)" + request += ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Широковещательные сообщения';" + _, err := tx.Exec(request) + if err != nil { + return err + } + + return nil +} + +func CreateBroadcastsDown(tx *sql.Tx) error { + _, err := tx.Exec("DROP TABLE `broadcasts`;") + if err != nil { + return err + } + return nil +} diff --git a/lib/migrations/migrations.go b/lib/migrations/migrations.go index 0006f85..1ff01be 100644 --- a/lib/migrations/migrations.go +++ b/lib/migrations/migrations.go @@ -33,6 +33,7 @@ func (m *Migrations) Init() { goose.AddNamedMigration("16_change_chat_type_column.go", ChangeChatTypeColumnUp, ChangeChatTypeColumnDown) goose.AddNamedMigration("17_change_profile_pokememes_columns.go", ChangeProfilePokememesColumnsUp, ChangeProfilePokememesColumnsDown) goose.AddNamedMigration("18_add_pokememes_wealth.go", AddPokememesWealthUp, AddPokememesWealthDown) + goose.AddNamedMigration("19_create_broadcasts.go", CreateBroadcastsUp, CreateBroadcastsDown) } func (m *Migrations) Migrate() error { diff --git a/lib/router/router.go b/lib/router/router.go index 450cb96..86fd137 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -50,6 +50,7 @@ func (r *Router) RouteRequest(update tgbotapi.Update) string { // Owner commands var sendAllMsg = regexp.MustCompile("/send_all(.+)") + var sendConfirmMsg = regexp.MustCompile(`/send_confirm(\s)(\d+)`) // Forwards var pokememeMsg = regexp.MustCompile("(Уровень)(.+)(Опыт)(.+)\n(Элементы:)(.+)\n(.+)(💙MP)") @@ -131,7 +132,13 @@ func (r *Router) RouteRequest(update tgbotapi.Update) string { // Admin commands case sendAllMsg.MatchString(text): if c.Getters.PlayerBetterThan(&playerRaw, "admin") { - c.Talkers.AdminBroadcastMessage(update) + 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) } diff --git a/lib/talkers/broadcast.go b/lib/talkers/broadcast.go index 7883016..2f20be4 100644 --- a/lib/talkers/broadcast.go +++ b/lib/talkers/broadcast.go @@ -3,16 +3,68 @@ package talkers -import ( // stdlib - // 3rd party +import ( + // stdlib + "strconv" "strings" - + // 3rd party "github.com/go-telegram-bot-api/telegram-bot-api" + // local + "../dbmapping" ) -// AdminBroadcastMessage sends message to all private chats with bot -func (t *Talkers) AdminBroadcastMessage(update tgbotapi.Update) string { - broadcastingMessageBody := strings.Replace(update.Message.Text, "/send_all", "", 1) +// AdminBroadcastMessageCompose saves message for future broadcast +func (t *Talkers) AdminBroadcastMessageCompose(update tgbotapi.Update, playerRaw *dbmapping.Player) string { + broadcastingMessageBody := strings.Replace(update.Message.Text, "/send_all ", "", 1) + + messageRaw, ok := c.Getters.CreateBroadcastMessage(playerRaw, broadcastingMessageBody, "all") + if !ok { + return "fail" + } + + message := "Сообщение сохранено в базу.\n" + message += "Выглядеть оно будет так:" + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + broadcastingMessage := "*Привет, %username%!*\n\n" + broadcastingMessage += "*Важное сообщение от администратора " + update.Message.From.FirstName + " " + update.Message.From.LastName + "* (@" + update.Message.From.UserName + ")\n\n" + broadcastingMessage += messageRaw.Text + + msg = tgbotapi.NewMessage(update.Message.Chat.ID, broadcastingMessage) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + message = "Чтобы отправить сообщение всем, отправь команду /send\\_confirm " + strconv.Itoa(messageRaw.ID) + + msg = tgbotapi.NewMessage(update.Message.Chat.ID, message) + msg.ParseMode = "Markdown" + + c.Bot.Send(msg) + + return "ok" +} + +// AdminBroadcastMessageSend sends saved message to all private chats +func (t *Talkers) AdminBroadcastMessageSend(update tgbotapi.Update, playerRaw *dbmapping.Player) string { + messageNum := strings.Replace(update.Message.Text, "/send_confirm ", "", 1) + messageNumInt, _ := strconv.Atoi(messageNum) + messageRaw, ok := c.Getters.GetBroadcastMessageByID(messageNumInt) + if !ok { + return "fail" + } + if messageRaw.AuthorID != playerRaw.ID { + return "fail" + } + if messageRaw.Status != "new" { + return "fail" + } + + broadcastingMessageBody := messageRaw.Text privateChats, ok := c.Getters.GetAllPrivateChats() if !ok { @@ -30,6 +82,11 @@ func (t *Talkers) AdminBroadcastMessage(update tgbotapi.Update) string { c.Bot.Send(msg) } + messageRaw, ok = c.Getters.UpdateBroadcastMessageStatus(messageRaw.ID, "sent") + if !ok { + return "fail" + } + message := "Сообщение всем отправлено. Надеюсь, пользователи бота за него тебя не убьют.\n" msg := tgbotapi.NewMessage(update.Message.Chat.ID, message) diff --git a/lib/talkers/talkersinterface/talkersinterface.go b/lib/talkers/talkersinterface/talkersinterface.go index 4c86fed..83c79e3 100644 --- a/lib/talkers/talkersinterface/talkersinterface.go +++ b/lib/talkers/talkersinterface/talkersinterface.go @@ -30,7 +30,8 @@ type TalkersInterface interface { AnyMessageUnauthorized(update tgbotapi.Update) GetterError(update tgbotapi.Update) - AdminBroadcastMessage(update tgbotapi.Update) string + AdminBroadcastMessageCompose(update tgbotapi.Update, playerRaw *dbmapping.Player) string + AdminBroadcastMessageSend(update tgbotapi.Update, playerRaw *dbmapping.Player) string DurakMessage(update tgbotapi.Update) MatMessage(update tgbotapi.Update)