diff --git a/src/manageAdmin.go b/src/manageAdmin.go index 65be555..26ca9cc 100644 --- a/src/manageAdmin.go +++ b/src/manageAdmin.go @@ -68,7 +68,6 @@ func removeBotAdmins() error { } return returnErr }) - //for _, token := range tokens { for _, botAdmin := range botAdmins { adminID, err := strconv.Atoi(botAdmin) if err != nil { @@ -86,6 +85,35 @@ func removeBotAdmins() error { return nil } +func hasBotAdmins() (bool, error) { + if redisClient == nil { + return false, ErrNilPointer + } + + adminNum, err := redisClient.SCard(adminUsers).Result() + if err != nil { + log.Printf("Error retrieving number of admins: %v", err) + return false, ErrRedisRetrieveSet + } + + if adminNum <= 0 { + return false, nil + } + return true, nil +} + +func isBotAdmin(userID int) (bool, error) { + if redisClient == nil { + return false, ErrNilPointer + } + admin, err := redisClient.SIsMember(adminUsers, strconv.Itoa(userID)).Result() + if err != nil { + log.Printf("Error checking if ID is bot admin: %v", err) + return false, ErrRedisCheckSet + } + return admin, nil +} + func addBotAdmin(newAdminID string) error { if redisClient == nil { return ErrNilPointer @@ -126,11 +154,24 @@ func addBotAdmin(newAdminID string) error { return ErrAddUser } } + isAuth, err := isAuthrizedUser(ID) + if err != nil { + log.Printf("Error checking if ID is authorized: %v", err) + return ErrAddAdmin + } + if !isAuth { + err = authorizeUser(ID, true) + if err != nil { + log.Printf("Error authorizing user: %v", err) + return ErrAddAuthUser + } + } err = redisClient.SAdd(adminUsers, adminID).Err() if err != nil { log.Printf("Error in adding new admin ID: %v", err) return ErrRedisAddSet } + botStatus.hasAdmin = true err = authorizeUser(ID, true) if err != nil { @@ -142,7 +183,7 @@ func addBotAdmin(newAdminID string) error { log.Printf("Error getting user info: %v", err) return ErrGetUser } - err = sendMessage(user, newAdminMsg) + err = sendMsg(user, newAdminMsg) if err != nil { log.Printf("Error sending message to new admin: %v", err) return ErrSendMsg @@ -165,11 +206,18 @@ func removeBotAdmin(adminID int) error { log.Printf("Error getting user info: %v", err) return ErrGetUser } - err = sendMessage(user, delAdminMsg) + err = sendMsg(user, delAdminMsg) if err != nil { log.Printf("Error sending message to removed admin: %v", err) return ErrSendMsg } + hasAdmin, err := hasBotAdmins() + if err != nil { + log.Printf("Error checking if bot has admins: %v", err) + return ErrRedisRetrieveSet + } + botStatus.hasAdmin = hasAdmin + return nil } diff --git a/src/manageUser.go b/src/manageUser.go index f8e7f86..55a8dfc 100644 --- a/src/manageUser.go +++ b/src/manageUser.go @@ -74,6 +74,38 @@ func getUserInfo(userID int) (*tb.User, error) { return jsonUser, nil } +func isStartedUser(userID int) (bool, error) { + if redisClient == nil { + return false, ErrNilPointer + } + started, err := redisClient.SIsMember(startedUsers, strconv.Itoa(userID)).Result() + if err != nil { + log.Printf("Error checking if user is started: %v", err) + return false, ErrRedisCheckSet + } + return started, nil +} + +func startUser(userID int, started bool) error { + if redisClient == nil { + return ErrNilPointer + } + if started { + err := redisClient.SAdd(startedUsers, strconv.Itoa(userID)).Err() + if err != nil { + log.Printf("Error adding token to set: %v", err) + return ErrRedisAddSet + } + } else { + err := redisClient.SRem(startedUsers, strconv.Itoa(userID)).Err() + if err != nil { + log.Printf("Error removing token from set: %v", err) + return ErrRedisRemSet + } + } + return nil +} + func isAuthrizedUser(userID int) (bool, error) { if redisClient == nil { return false, ErrNilPointer diff --git a/src/redisAPI.go b/src/redisAPI.go index f77ed54..3cf4ff5 100644 --- a/src/redisAPI.go +++ b/src/redisAPI.go @@ -8,13 +8,14 @@ import ( ) const ( - botToken = "botToken" - botInfo = "botInfo" - usersID = "usersID" - usersInfo = "usersInfo" - usersGroups = "usersGroups" - authUsers = "authUsers" - adminUsers = "adminUsers" + botToken = "botToken" + botInfo = "botInfo" + usersID = "usersID" + usersInfo = "usersInfo" + usersGroups = "usersGroups" + startedUsers = "startedUsers" + authUsers = "authUsers" + adminUsers = "adminUsers" ) var redisClient *redis.Client diff --git a/src/sys.go b/src/sys.go index 493990d..79ff571 100644 --- a/src/sys.go +++ b/src/sys.go @@ -82,7 +82,7 @@ func getFlags() error { } func exit() error { - if isStartedBot { + if botStatus.isStarted { log.Printf("Stopping %s", bot.Me.Username) bot.Stop() log.Println("Bot stopped") @@ -99,7 +99,7 @@ func exit() error { func mainMenu() *wmenu.Menu { menu := wmenu.NewMenu("What do you want to do?") menu.LoopOnInvalid() - if !isStartedBot { + if !botStatus.isStarted { menu.Option("Start bot", nil, true, func(opt wmenu.Opt) error { return botStart() }) @@ -107,7 +107,7 @@ func mainMenu() *wmenu.Menu { return setBotToken("") }) } - if isStartedBot { + if botStatus.isStarted { menu.Option("Stop bot", nil, true, func(opt wmenu.Opt) error { return botStop() }) @@ -115,9 +115,11 @@ func mainMenu() *wmenu.Menu { menu.Option("Add bot admin(s)", nil, false, func(opt wmenu.Opt) error { return addBotAdmins(nil) }) - menu.Option("Remove bot admin(s)", nil, false, func(opt wmenu.Opt) error { - return removeBotAdmins() - }) + if botStatus.hasAdmin { + menu.Option("Remove bot admin(s)", nil, false, func(opt wmenu.Opt) error { + return removeBotAdmins() + }) + } menu.Option("Exit", nil, false, func(opt wmenu.Opt) error { return exit() }) @@ -127,15 +129,17 @@ func mainMenu() *wmenu.Menu { func mainMenuLoop() error { menu := mainMenu() - botStatus := isStartedBot + botStarted := botStatus.isStarted + hasAdmin := botStatus.hasAdmin fmt.Println(welcomeMessage) for { err := menu.Run() if err != nil { log.Printf("Error in main menu: %v", err) } - if botStatus != isStartedBot { - botStatus = isStartedBot + if botStarted != botStatus.isStarted || hasAdmin != botStatus.hasAdmin { + botStarted = botStatus.isStarted + hasAdmin = botStatus.hasAdmin menu = mainMenu() } } diff --git a/src/telegramAPI.go b/src/telegramAPI.go index 303c0b4..2c9b48a 100644 --- a/src/telegramAPI.go +++ b/src/telegramAPI.go @@ -3,20 +3,43 @@ package main import ( "errors" "log" - "strconv" "time" tb "gopkg.in/tucnak/telebot.v2" ) +type botBool struct { + isStarted bool + hasAdmin bool +} + const ( - newAdminMsg string = "Sei stato aggiunto come amministratore. Adesso hai a disposizione una serie aggiuntiva di comandi e controlli per il bot." - delAdminMsg string = "Sei stato rimosso da amministratore." + startMsg string = "Salve, sono Stefano, il Magister! Come posso esservi d'aiuto?" + alreadyStartedMsg string = "Si, mi dica, che c'è?! Sono qui!" + restartMsg string = "Eccomi, sono tornato! Ha bisogno? Mi dica pure!" + stopMsg string = "Mi assenterò per qualche istante, d'altra parte anch'io ho pur diritto alla mia vita privata. Masino mi attende!" + newAdminMsg string = "Beh allora, vediamo... Ah si, la nomino amministratore! Da grandi poteri derivano grandi responsabilità. Mi raccomando, non me ne faccia pentire!" + delAdminMsg string = "Ecco, che le avevo detto?! Mi sembrava di essere stato chiaro! Dovrò sollevarla dall'incarico... Mi spiace molto ma da ora in avanti non sarà più amministratore" ) -var bot *tb.Bot -var isStartedBot bool +var genericCommands = map[string]bool{ + "/start": true, + "/stop": true, + "/menu": true, + "/prossimoEvento": true, +} +var authCommands = map[string]bool{ + "/prossimaProvaSezione": true, + "/prossimaProvaInsieme": true, +} +var adminCommands = map[string]bool{ + "/autorizzaUtente": true, + "/aggiungiAdmin": true, + "/rimuoviAdmin": true, +} +var bot *tb.Bot +var botStatus botBool var ( //ErrNilPointer is thrown when a pointer is nil ErrNilPointer = errors.New("pointer is nil") @@ -42,32 +65,7 @@ func botInit() error { } poller := &tb.LongPoller{Timeout: 15 * time.Second} - middlePoller := tb.NewMiddlewarePoller(poller, func(upd *tb.Update) bool { - if upd.Message == nil { - return true - } - if upd.Message.Sender != nil { - err := addUser(upd.Message.Sender) - if err != nil { - log.Printf("Error in adding user info: %v", err) - } - err = authorizeUser(upd.Message.Sender.ID, true) - if err != nil { - log.Printf("Error in authorizing user: %v", err) - } - } else { - log.Printf("%v", ErrIDFromMsg) - } - auth, err := isAuthrizedUser(upd.Message.Sender.ID) - if err != nil { - log.Printf("Error checking if user is authorized: %v", err) - } - if !auth { - return false - } - - return true - }) + middlePoller := tb.NewMiddlewarePoller(poller, setBotPoller) bot, err = tb.NewBot(tb.Settings{ Token: token, @@ -84,15 +82,35 @@ func botInit() error { return ErrBotInit } + err = setBotMenus() + if err != nil { + log.Printf("Error setting bot menus: %v", err) + return ErrBotInit + } + + err = setBotCallbacks() + if err != nil { + log.Printf("Error setting bot callbacks: %v", err) + return ErrBotInit + } + err = addBotInfo(token, bot.Me.Username) if err != nil { log.Printf("Error: bot %s info couldn't be added: %v", bot.Me.Username, err) + return ErrBotInit } + hasAdmin, err := hasBotAdmins() + if err != nil { + log.Printf("Error checking if bot has admins: %v", err) + return ErrBotInit + } + botStatus.hasAdmin = hasAdmin + return nil } -func sendMessage(user *tb.User, msg string) error { +func sendMsg(user *tb.User, msg string) error { _, err := bot.Send(user, msg) if err != nil { log.Printf("Error sending message to user: %v", err) @@ -101,28 +119,97 @@ func sendMessage(user *tb.User, msg string) error { return nil } -func setBotHandlers() error { - if bot == nil { - return ErrNilPointer +func sendMsgWithMenu(user *tb.User, msg string) error { + var menu [][]tb.InlineButton + + auth, err := isAuthrizedUser(user.ID) + if err != nil { + log.Printf("Error checking if user is authorized: %v", err) + } + admin, err := isBotAdmin(user.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) } - bot.Handle("/hello", func(m *tb.Message) { - bot.Send(m.Sender, "hello world") + if admin { + menu = adminInlineMenu + } else if auth { + menu = authInlineMenu + } else { + menu = genericInlineMenu + } + _, err = bot.Send(user, msg, &tb.ReplyMarkup{ + InlineKeyboard: menu, }) - bot.Handle("/userID", func(m *tb.Message) { - bot.Send(m.Sender, strconv.Itoa(m.Sender.ID)) - }) - + if err != nil { + log.Printf("Error sending message to user: %v", err) + return ErrSendMsg + } return nil } +func sendMsgWithSpecificMenu(user *tb.User, msg string, menu [][]tb.InlineButton) error { + _, err := bot.Send(user, msg, &tb.ReplyMarkup{ + InlineKeyboard: menu, + }) + if err != nil { + log.Printf("Error sending message to user: %v", err) + return ErrSendMsg + } + return nil +} + +func setBotPoller(upd *tb.Update) bool { + if upd.Message == nil { + return true + } + if upd.Message.Sender != nil { + err := addUser(upd.Message.Sender) + if err != nil { + log.Printf("Error in adding user info: %v", err) + } + } else { + log.Printf("%v", ErrIDFromMsg) + } + _, isGenericCmd := genericCommands[upd.Message.Text] + _, isAuthCmd := authCommands[upd.Message.Text] + _, isAdminCmd := adminCommands[upd.Message.Text] + + started, err := isStartedUser(upd.Message.Sender.ID) + if err != nil { + log.Printf("Error checking if user is started: %v", err) + } + if !started && upd.Message.Text != "/start" { + return false + } + + auth, err := isAuthrizedUser(upd.Message.Sender.ID) + if err != nil { + log.Printf("Error checking if user is authorized: %v", err) + } + admin, err := isBotAdmin(upd.Message.Sender.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) + } + if isAdminCmd && !admin { + return false + } + if isAuthCmd && !auth { + return false + } + if !isGenericCmd { + return false + } + return true +} + func botStart() error { if bot == nil { return ErrNilPointer } go bot.Start() - isStartedBot = true + botStatus.isStarted = true log.Printf("Started %s", bot.Me.Username) return nil @@ -134,7 +221,7 @@ func botStop() error { } log.Printf("Stopping %s", bot.Me.Username) bot.Stop() - isStartedBot = false + botStatus.isStarted = false log.Println("Bot stopped") return nil diff --git a/src/telegramHandlers.go b/src/telegramHandlers.go new file mode 100644 index 0000000..b1d7d38 --- /dev/null +++ b/src/telegramHandlers.go @@ -0,0 +1,79 @@ +package main + +import ( + "log" + "strconv" + + tb "gopkg.in/tucnak/telebot.v2" +) + +func startHandler(m *tb.Message) { + var msg string + + isUser, err := isUser(m.Sender.ID) + if err != nil { + log.Printf("Error checking if ID is bot user: %v", err) + } + + started, err := isStartedUser(m.Sender.ID) + if err != nil { + log.Printf("Error checking if user is started: %v", err) + } + if !started { + err = startUser(m.Sender.ID, true) + if err != nil { + log.Printf("Error starting user: %v", err) + } + if isUser { + msg = restartMsg + } else { + msg = startMsg + } + } else { + msg = alreadyStartedMsg + } + + err = sendMsgWithMenu(m.Sender, msg) + if err != nil { + log.Printf("Error sending message to started user: %v", err) + } +} + +func stopHandler(m *tb.Message) { + admin, err := isBotAdmin(m.Sender.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) + } + if admin { + msg := "Non ci siamo... Io l'ho nominata AMMINISTRATORE, cosa crede?! Questo ruolo esige impegno! Non può certo bloccarmi!" + err := sendMsg(m.Sender, msg) + if err != nil { + log.Printf("Error sending message to unstoppable user: %v", err) + } + } else { + err = startUser(m.Sender.ID, false) + if err != nil { + log.Printf("Error starting user: %v", err) + } + err := sendMsgWithSpecificMenu(m.Sender, stopMsg, startMenu) + if err != nil { + log.Printf("Error sending message to stopped user: %v", err) + } + } +} + +func setBotHandlers() error { + if bot == nil { + return ErrNilPointer + } + bot.Handle("/start", startHandler) + bot.Handle("/stop", stopHandler) + bot.Handle("/menu", func(m *tb.Message) { + bot.Send(m.Sender, "hello world") + }) + bot.Handle("/userID", func(m *tb.Message) { + bot.Send(m.Sender, strconv.Itoa(m.Sender.ID)) + }) + + return nil +} diff --git a/src/telegramMenu.go b/src/telegramMenu.go new file mode 100644 index 0000000..ef816d1 --- /dev/null +++ b/src/telegramMenu.go @@ -0,0 +1,87 @@ +package main + +import ( + "log" + + tb "gopkg.in/tucnak/telebot.v2" +) + +var ( + adminInlineMenu [][]tb.InlineButton + authInlineMenu [][]tb.InlineButton + genericInlineMenu [][]tb.InlineButton + startMenu [][]tb.InlineButton +) + +var ( + startBtn = tb.InlineButton{ + Unique: "start_btn", + Text: "\xE2\x96\xB6 Avvia il barandaBot", + } + stopBtn = tb.InlineButton{ + Unique: "stop_btn", + Text: "\xF0\x9F\x9A\xAB Ferma il barandaBot", + } + infoBtn = tb.InlineButton{ + Unique: "info_btn", + Text: "\xE2\x84\xB9 Info", + } +) + +func setBotMenus() error { + adminInlineMenu = append(adminInlineMenu, []tb.InlineButton{stopBtn, infoBtn}) + + authInlineMenu = append(authInlineMenu, []tb.InlineButton{stopBtn, infoBtn}) + + genericInlineMenu = append(genericInlineMenu, []tb.InlineButton{stopBtn, infoBtn}) + + startMenu = append(startMenu, []tb.InlineButton{startBtn}) + + return nil +} + +func setBotCallbacks() error { + if bot == nil { + return ErrNilPointer + } + + bot.Handle(&startBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + //TODO: save last message id per user so it's possible to hide inline keyboard + //bot.Edit(lastMsgID, tb.ReplyMarkup{}) + err := startUser(c.Sender.ID, true) + if err != nil { + log.Printf("Error starting user: %v", err) + } + err = sendMsgWithMenu(c.Sender, restartMsg) + if err != nil { + log.Printf("Error sending message to started user: %v", err) + } + }) + + bot.Handle(&stopBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + admin, err := isBotAdmin(c.Sender.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) + } + if admin { + msg := "Non ci siamo... Io l'ho nominata AMMINISTRATORE, cosa crede?! Questo ruolo esige impegno! Non può certo bloccarmi!" + err := sendMsg(c.Sender, msg) + if err != nil { + log.Printf("Error sending message to unstoppable user: %v", err) + } + } else { + err = startUser(c.Sender.ID, false) + if err != nil { + log.Printf("Error starting user: %v", err) + } + err := sendMsgWithSpecificMenu(c.Sender, stopMsg, startMenu) + if err != nil { + log.Printf("Error sending message to stopped user: %v", err) + } + } + }) + + return nil +}