diff --git a/src/manageMsg.go b/src/manageMsg.go new file mode 100644 index 0000000..c827d69 --- /dev/null +++ b/src/manageMsg.go @@ -0,0 +1,154 @@ +package main + +import ( + "encoding/json" + "log" + "strconv" + + tb "gopkg.in/tucnak/telebot.v2" +) + +func closeMsgMenu(storedMsg *tb.StoredMessage) error { + _, err := bot.EditReplyMarkup(storedMsg, &tb.ReplyMarkup{ + InlineKeyboard: nil, + }) + if err != nil { + log.Printf("Error modifying the message: %v", err) + } + + return nil +} + +func setLastMsgPerUser(userID int, msg *tb.Message) error { + storedMsg := tb.StoredMessage{ + MessageID: strconv.Itoa(msg.ID), + ChatID: msg.Chat.ID} + + jsonMsg, err := json.Marshal(storedMsg) + if err != nil { + log.Printf("Error in marshalling msg to json: %v", err) + return ErrJSONMarshall + } + err = redisClient.HSet(lastMsgPerUser, strconv.Itoa(userID), jsonMsg).Err() + if err != nil { + log.Printf("Error adding last message per user info in hash: %v", err) + return ErrRedisAddHash + } + + return nil +} + +func getLastMsgPerUser(userID int) (*tb.StoredMessage, error) { + msg, err := redisClient.HGet(lastMsgPerUser, strconv.Itoa(userID)).Result() + if err != nil { + log.Printf("Error retriving last msg per user info from hash: %v", err) + return nil, ErrRedisRetrieveHash + } + jsonMsg := &tb.StoredMessage{} + err = json.Unmarshal([]byte(msg), jsonMsg) + if err != nil { + log.Printf("Error unmarshalling last msg per user info: %v", err) + return nil, ErrJSONUnmarshall + } + return jsonMsg, nil +} + +func sendMsg(user *tb.User, msg string) error { + sentMsg, err := bot.Send(user, msg, &tb.SendOptions{ + ParseMode: "Markdown", + }) + if err != nil { + log.Printf("Error sending message to user: %v", err) + return ErrSendMsg + } + storedMsg, err := getLastMsgPerUser(user.ID) + if err != nil { + log.Printf("Error retriving last message per user: %v", err) + } else { + err = closeMsgMenu(storedMsg) + if err != nil { + log.Printf("Error modifying the message: %v", err) + } + } + err = setLastMsgPerUser(user.ID, sentMsg) + if err != nil { + log.Printf("Error setting last msg per user: %v", err) + return ErrSetLastMsg + } + return nil +} + +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) + } + + if admin { + menu = adminInlineMenu + } else if auth { + menu = authInlineMenu + } else { + menu = genericInlineMenu + } + sentMsg, err := bot.Send(user, msg, &tb.SendOptions{ + ReplyMarkup: &tb.ReplyMarkup{ + InlineKeyboard: menu, + }, + ParseMode: "Markdown", + }) + if err != nil { + log.Printf("Error sending message to user: %v", err) + return ErrSendMsg + } + storedMsg, err := getLastMsgPerUser(user.ID) + if err != nil { + log.Printf("Error retriving last message per user: %v", err) + } else { + err = closeMsgMenu(storedMsg) + if err != nil { + log.Printf("Error modifying the message: %v", err) + } + } + err = setLastMsgPerUser(user.ID, sentMsg) + if err != nil { + log.Printf("Error setting last msg per user: %v", err) + return ErrSetLastMsg + } + return nil +} + +func sendMsgWithSpecificMenu(user *tb.User, msg string, menu [][]tb.InlineButton) error { + sentMsg, err := bot.Send(user, msg, &tb.SendOptions{ + ReplyMarkup: &tb.ReplyMarkup{ + InlineKeyboard: menu, + }, + ParseMode: "Markdown", + }) + if err != nil { + log.Printf("Error sending message to user: %v", err) + return ErrSendMsg + } + storedMsg, err := getLastMsgPerUser(user.ID) + if err != nil { + log.Printf("Error retriving last message per user: %v", err) + } else { + err = closeMsgMenu(storedMsg) + if err != nil { + log.Printf("Error modifying the message: %v", err) + } + } + err = setLastMsgPerUser(user.ID, sentMsg) + if err != nil { + log.Printf("Error setting last msg per user: %v", err) + return ErrSetLastMsg + } + + return nil +} diff --git a/src/manageUser.go b/src/manageUser.go index 55a8dfc..13071e8 100644 --- a/src/manageUser.go +++ b/src/manageUser.go @@ -12,7 +12,8 @@ import ( type userGroup int const ( - ugSoprano userGroup = iota + ugEsterno userGroup = iota + ugSoprano ugContralto ugTenore ugBasso @@ -41,6 +42,12 @@ func addUser(user *tb.User) error { return ErrRedisAddHash } + err = setUserGroups(user.ID, ugEsterno) + if err != nil { + log.Printf("Error setting user default group: %v", err) + return ErrRedisAddSet + } + return nil } @@ -86,11 +93,11 @@ func isStartedUser(userID int) (bool, error) { return started, nil } -func startUser(userID int, started bool) error { +func startUser(userID int, start bool) error { if redisClient == nil { return ErrNilPointer } - if started { + if start { err := redisClient.SAdd(startedUsers, strconv.Itoa(userID)).Err() if err != nil { log.Printf("Error adding token to set: %v", err) @@ -145,6 +152,11 @@ func setUserGroups(userID int, groups ...userGroup) error { var csvGroups string for _, group := range groups { csvGroups += strconv.Itoa(int(group)) + "," + err := redisClient.SAdd("ug"+strconv.Itoa(int(group)), strconv.Itoa(userID)).Err() + if err != nil { + log.Printf("Error adding user to usergroup set: %v", err) + return ErrRedisAddSet + } } err := redisClient.HSet(usersGroups, strconv.Itoa(userID), csvGroups).Err() if err != nil { @@ -162,7 +174,7 @@ func getUserGroups(userID int) ([]userGroup, error) { csvGroups, err := redisClient.HGet(usersGroups, strconv.Itoa(userID)).Result() if err != nil { - log.Printf("Error retrieving user groups: %v", err) + log.Printf("Error retriving user groups: %v", err) return nil, ErrRedisRetrieveHash } var retGroups []userGroup @@ -177,3 +189,47 @@ func getUserGroups(userID int) ([]userGroup, error) { } return retGroups, nil } + +func getUsersInGroup(group userGroup) ([]int, error) { + users, err := redisClient.SMembers("ug" + strconv.Itoa(int(group))).Result() + if err != nil { + log.Printf("Error retriving users in group: %v", err) + return nil, ErrRedisRetrieveSet + } + var retUsers []int + for _, user := range users { + intUser, err := strconv.Atoi(user) + if err != nil { + log.Printf("Error converting user ID: %v", err) + return nil, ErrAtoiConv + } + retUsers = append(retUsers, intUser) + } + return retUsers, nil +} + +func convertUserGroups(groups []userGroup) []string { + var stringGroups []string + for _, group := range groups { + switch group { + case ugEsterno: + stringGroups = append(stringGroups, "Esterno al coro") + case ugSoprano: + stringGroups = append(stringGroups, "Soprano") + case ugContralto: + stringGroups = append(stringGroups, "Contralto") + case ugTenore: + stringGroups = append(stringGroups, "Tenore") + case ugBasso: + stringGroups = append(stringGroups, "Basso") + case ugCommissario: + stringGroups = append(stringGroups, "Commissario") + case ugReferente: + stringGroups = append(stringGroups, "Referente") + case ugPreparatore: + stringGroups = append(stringGroups, "Preparatore") + } + } + + return stringGroups +} diff --git a/src/redisAPI.go b/src/redisAPI.go index 3cf4ff5..82b286d 100644 --- a/src/redisAPI.go +++ b/src/redisAPI.go @@ -8,14 +8,15 @@ import ( ) const ( - botToken = "botToken" - botInfo = "botInfo" - usersID = "usersID" - usersInfo = "usersInfo" - usersGroups = "usersGroups" - startedUsers = "startedUsers" - authUsers = "authUsers" - adminUsers = "adminUsers" + botToken = "botToken" + botInfo = "botInfo" + usersID = "usersID" + usersInfo = "usersInfo" + usersGroups = "usersGroups" + startedUsers = "startedUsers" + authUsers = "authUsers" + adminUsers = "adminUsers" + lastMsgPerUser = "lastMsgPerUser" ) var redisClient *redis.Client diff --git a/src/sys.go b/src/sys.go index 79ff571..8adaeba 100644 --- a/src/sys.go +++ b/src/sys.go @@ -54,7 +54,7 @@ func (i *stringSlice) Set(values string) error { func getFlags() error { const ( defaultInteractive = true - interactiveUsage = "False if the bot isn't executed on a tty" + interactiveUsage = "true/false if you want interactive/non-interactive usage" defaultAddr = "127.0.0.1:6379" addrUsage = "The address of the redis instance" defaultPwd = "" diff --git a/src/telegramAPI.go b/src/telegramAPI.go index 2c9b48a..1a134b8 100644 --- a/src/telegramAPI.go +++ b/src/telegramAPI.go @@ -18,26 +18,17 @@ const ( 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!" + unstoppableMsg string = "Non ci siamo... Io l'ho nominata AMMINISTRATORE, cosa crede?! Questo ruolo esige impegno! Non può certo bloccarmi!" 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" + menuMsg string = "Ecco a lei, questo è l'elenco di tutto ciò che può chiedermi. Non mi disturbi con altre richieste!" + contactMsg string = "*BarandaBot*" + + "\nSe hai domande, suggerimenti o se vuoi segnalare bug e altri malfunzionamenti puoi contattare l'Altissimo con i seguenti mezzi di comunicazione:" + + "\n- \xF0\x9F\x90\xA6 _Piccione viaggiatore_: PlusCode - P99W+4Q Pisa, PI" + + "\n- \xF0\x9F\x93\xA7 _Mail_: telebot.corounipi@gmail.com" + + "\n- \xF0\x9F\x93\x82 _GitHub_: https://github.com/Noettore/barandaBot" ) -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 ( @@ -55,6 +46,8 @@ var ( ErrBotInit = errors.New("telegram: error in bot initialization") //ErrBotConn is thrown when there is a connection problem ErrBotConn = errors.New("telegram: cannot connect to bot") + //ErrSetLastMsg is thrown when it's not possible to set last message per user in hash + ErrSetLastMsg = errors.New("cannot set last message per user") ) func botInit() error { @@ -110,55 +103,6 @@ func botInit() error { return nil } -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) - return ErrSendMsg - } - return nil -} - -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) - } - - if admin { - menu = adminInlineMenu - } else if auth { - menu = authInlineMenu - } else { - menu = genericInlineMenu - } - _, 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 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 diff --git a/src/telegramCommands.go b/src/telegramCommands.go new file mode 100644 index 0000000..4b50fcb --- /dev/null +++ b/src/telegramCommands.go @@ -0,0 +1,118 @@ +package main + +import ( + "log" + "strconv" + + tb "gopkg.in/tucnak/telebot.v2" +) + +var genericCommands = map[string]bool{ + "/start": true, + "/stop": true, + "/menu": true, + "/userInfo": true, + "/botInfo": true, + "/prossimoEvento": true, +} +var authCommands = map[string]bool{ + "/prossimaProvaSezione": true, + "/prossimaProvaInsieme": true, +} +var adminCommands = map[string]bool{ + "/authUser": true, + "/addAdmin": true, + "/delAdmin": true, +} + +func startCmd(u *tb.User) { + var msg string + + isUser, err := isUser(u.ID) + if err != nil { + log.Printf("Error checking if ID is bot user: %v", err) + } + + started, err := isStartedUser(u.ID) + if err != nil { + log.Printf("Error checking if user is started: %v", err) + } + if !started { + err = startUser(u.ID, true) + if err != nil { + log.Printf("Error starting user: %v", err) + } + if isUser { + msg = restartMsg + } else { + msg = startMsg + } + } else { + msg = alreadyStartedMsg + } + + err = sendMsgWithMenu(u, msg) + if err != nil { + log.Printf("Error sending message to started user: %v", err) + } +} + +func stopCmd(u *tb.User) { + admin, err := isBotAdmin(u.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) + } + if admin { + err := sendMsgWithMenu(u, unstoppableMsg) + if err != nil { + log.Printf("Error sending message to unstoppable user: %v", err) + } + } else { + err = startUser(u.ID, false) + if err != nil { + log.Printf("Error starting user: %v", err) + } + err := sendMsgWithSpecificMenu(u, stopMsg, startMenu) + if err != nil { + log.Printf("Error sending message to stopped user: %v", err) + } + } +} + +func userInfoCmd(u *tb.User) { + userGroups, err := getUserGroups(u.ID) + if err != nil { + log.Printf("Error retriving user groups: %v", err) + } + stringGroups := convertUserGroups(userGroups) + + isAdmin, err := isBotAdmin(u.ID) + if err != nil { + log.Printf("Error checking if user is admin: %v", err) + } + isAuth, err := isAuthrizedUser(u.ID) + if err != nil { + log.Printf("Error checking if user is authorized: %v", err) + } + + msg := "\xF0\x9F\x91\xA4 *INFORMAZIONI UTENTE*" + + "\n- *Nome*: " + u.FirstName + + "\n- *Username*: " + u.Username + + "\n- *ID*: " + strconv.Itoa(u.ID) + + "\n- *Gruppi*: " + + for _, group := range stringGroups { + msg += group + ", " + } + + msg += "\n- *Tipo utente*: " + + if isAdmin { + msg += "Admin" + } else if isAuth { + msg += "Autorizzato" + } else { + msg += "Utente semplice" + } + err = sendMsgWithSpecificMenu(u, msg, goBackMenu) +} diff --git a/src/telegramHandlers.go b/src/telegramHandlers.go index b1d7d38..525b2da 100644 --- a/src/telegramHandlers.go +++ b/src/telegramHandlers.go @@ -1,78 +1,27 @@ 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("/start", func(m *tb.Message) { + startCmd(m.Sender) }) - bot.Handle("/userID", func(m *tb.Message) { - bot.Send(m.Sender, strconv.Itoa(m.Sender.ID)) + bot.Handle("/stop", func(m *tb.Message) { + stopCmd(m.Sender) + }) + bot.Handle("/menu", func(m *tb.Message) { + sendMsgWithMenu(m.Sender, menuMsg) + }) + bot.Handle("/userInfo", func(m *tb.Message) { + userInfoCmd(m.Sender) + }) + bot.Handle("/botInfo", func(m *tb.Message) { + sendMsgWithSpecificMenu(m.Sender, contactMsg, goBackMenu) }) return nil diff --git a/src/telegramMenu.go b/src/telegramMenu.go deleted file mode 100644 index ef816d1..0000000 --- a/src/telegramMenu.go +++ /dev/null @@ -1,87 +0,0 @@ -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 -} diff --git a/src/telegramMenus.go b/src/telegramMenus.go new file mode 100644 index 0000000..d0e6821 --- /dev/null +++ b/src/telegramMenus.go @@ -0,0 +1,81 @@ +package main + +import ( + tb "gopkg.in/tucnak/telebot.v2" +) + +var ( + adminInlineMenu [][]tb.InlineButton + authInlineMenu [][]tb.InlineButton + genericInlineMenu [][]tb.InlineButton + startMenu [][]tb.InlineButton + goBackMenu [][]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", + } + backBtn = tb.InlineButton{ + Unique: "back_btn", + Text: "\xF0\x9F\x94\x99 Torna al menù principale", + } + infoBtn = tb.InlineButton{ + Unique: "info_btn", + Text: "\xE2\x84\xB9 Info", + } + userBtn = tb.InlineButton{ + Unique: "user_btn", + Text: "\xF0\x9F\x91\xA4 My info", + } +) + +func setBotMenus() error { + + genericInlineMenu = append(genericInlineMenu, []tb.InlineButton{userBtn, infoBtn}, []tb.InlineButton{stopBtn}) + authInlineMenu = genericInlineMenu + adminInlineMenu = genericInlineMenu + //adminInlineMenu = append(adminInlineMenu, []tb.InlineButton{stopBtn, infoBtn}) + //authInlineMenu = append(authInlineMenu, []tb.InlineButton{stopBtn, infoBtn}) + + startMenu = append(startMenu, []tb.InlineButton{startBtn}) + goBackMenu = append(goBackMenu, []tb.InlineButton{backBtn}) + + return nil +} + +func setBotCallbacks() error { + if bot == nil { + return ErrNilPointer + } + + bot.Handle(&startBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + startCmd(c.Sender) + }) + + bot.Handle(&stopBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + stopCmd(c.Sender) + }) + + bot.Handle(&userBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + userInfoCmd(c.Sender) + }) + bot.Handle(&infoBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + sendMsgWithSpecificMenu(c.Sender, contactMsg, goBackMenu) + }) + bot.Handle(&backBtn, func(c *tb.Callback) { + bot.Respond(c, &tb.CallbackResponse{}) + sendMsgWithMenu(c.Sender, menuMsg) + }) + + return nil +}