diff --git a/src/barandaBot.go b/src/barandaBot.go index f30fac1..83a2dff 100644 --- a/src/barandaBot.go +++ b/src/barandaBot.go @@ -12,13 +12,19 @@ func main() { } err = redisInit(cmdFlags.redisAddr, cmdFlags.redisPwd, cmdFlags.redisDB) - defer redisClient.Close() if err != nil { log.Fatalf("Error in initializing redis instance: %v", err) } + defer redisClient.Close() + + err = botInit() + if err != nil { + log.Fatalf("Error initializing bot: %v", err) + } + defer bot.Stop() if cmdFlags.interactive { - mainMenu() + mainMenuLoop() } else if cmdFlags.token != "" { err = setBotToken(cmdFlags.token) if err == ErrAddToken { diff --git a/src/manageAdmin.go b/src/manageAdmin.go index f23fa56..65be555 100644 --- a/src/manageAdmin.go +++ b/src/manageAdmin.go @@ -45,7 +45,7 @@ func removeBotAdmins() error { if redisClient == nil { return ErrNilPointer } - botAdmins, err := redisClient.SMembers(adminSet).Result() + botAdmins, err := redisClient.SMembers(adminUsers).Result() if err != nil { log.Printf("Couldn't retrieve admins: %v", err) return ErrRedisRetrieveSet @@ -120,13 +120,13 @@ func addBotAdmin(newAdminID string) error { return ErrAddAdmin } if !isUser { - err = addUser(&tb.User{int(chat.ID), chat.FirstName, chat.LastName, chat.Username}) + err = addUser(&tb.User{ID: int(chat.ID), FirstName: chat.FirstName, LastName: chat.LastName, Username: chat.Username}) if err != nil { log.Printf("Error adding user: %v", err) return ErrAddUser } } - err = redisClient.SAdd(adminSet, adminID).Err() + err = redisClient.SAdd(adminUsers, adminID).Err() if err != nil { log.Printf("Error in adding new admin ID: %v", err) return ErrRedisAddSet @@ -142,7 +142,7 @@ func addBotAdmin(newAdminID string) error { log.Printf("Error getting user info: %v", err) return ErrGetUser } - err = sendMessage(user, "Sei stato aggiunto come amministratore del BarandaBot") + err = sendMessage(user, newAdminMsg) if err != nil { log.Printf("Error sending message to new admin: %v", err) return ErrSendMsg @@ -155,10 +155,21 @@ func removeBotAdmin(adminID int) error { if redisClient == nil { return ErrNilPointer } - err := redisClient.SRem(adminSet, strconv.Itoa(adminID)).Err() + err := redisClient.SRem(adminUsers, strconv.Itoa(adminID)).Err() if err != nil { log.Printf("Error removing admin from set: %v", err) return ErrRedisRemSet } + user, err := getUserInfo(adminID) + if err != nil { + log.Printf("Error getting user info: %v", err) + return ErrGetUser + } + err = sendMessage(user, delAdminMsg) + if err != nil { + log.Printf("Error sending message to removed admin: %v", err) + return ErrSendMsg + } + return nil } diff --git a/src/manageBot.go b/src/manageBot.go index aa359a4..5159842 100644 --- a/src/manageBot.go +++ b/src/manageBot.go @@ -2,14 +2,11 @@ package main import ( "bufio" - "encoding/json" "fmt" "log" "os" "regexp" "strings" - - tb "gopkg.in/tucnak/telebot.v2" ) func setBotToken(newToken string) error { @@ -49,12 +46,12 @@ func getBotToken() (string, error) { if redisClient == nil { return "", ErrNilPointer } - token, err := redisClient.Get(botToken).Result() + tokenExists, err := redisClient.Exists(botToken).Result() if err != nil { - log.Printf("Couldn't retrieve bot token: %v", err) - return "", ErrRedisRetrieveSet + log.Printf("Error checking if token exists in db: %v", err) + return "", ErrRedisCheckSet } - if token == "" { + if tokenExists == 0 { fmt.Println("No bot token found.") err := setBotToken("") if err != nil { @@ -62,20 +59,19 @@ func getBotToken() (string, error) { return "", ErrAddToken } } - + token, err := redisClient.Get(botToken).Result() + if err != nil { + log.Printf("Couldn't retrieve bot token: %v", err) + return "", ErrRedisRetrieveSet + } return token, nil } -func addBotInfo(botToken string, bot *tb.Bot) error { +func addBotInfo(botToken string, botUser string) error { if redisClient == nil { return ErrNilPointer } - jsonBot, err := json.Marshal(&bot) - if err != nil { - log.Printf("Error marshalling bot info: %v", err) - return ErrJSONMarshall - } - err = redisClient.HSet(botHash, botToken, string(jsonBot)).Err() + err := redisClient.HSet(botInfo, botToken, botUser).Err() if err != nil { log.Printf("Error in adding bot info: %v", err) return ErrRedisAddHash @@ -88,7 +84,7 @@ func removeBotInfo(botToken string) error { if redisClient == nil { return ErrNilPointer } - err := redisClient.HDel(botHash, botToken).Err() + err := redisClient.HDel(botInfo, botToken).Err() if err != nil { log.Printf("Error in removing bot info: %v", err) return ErrRedisDelHash diff --git a/src/manageUser.go b/src/manageUser.go index 99614e7..f8e7f86 100644 --- a/src/manageUser.go +++ b/src/manageUser.go @@ -4,15 +4,28 @@ import ( "encoding/json" "log" "strconv" + "strings" tb "gopkg.in/tucnak/telebot.v2" ) +type userGroup int + +const ( + ugSoprano userGroup = iota + ugContralto + ugTenore + ugBasso + ugCommissario + ugReferente + ugPreparatore +) + func addUser(user *tb.User) error { if redisClient == nil { return ErrNilPointer } - err := redisClient.SAdd(userSet, user.ID).Err() + err := redisClient.SAdd(usersID, user.ID).Err() if err != nil { log.Printf("Error in adding user ID: %v", err) return ErrRedisAddSet @@ -22,7 +35,7 @@ func addUser(user *tb.User) error { log.Printf("Error in marshalling user to json: %v", err) return ErrJSONMarshall } - err = redisClient.HSet(userHash, strconv.Itoa(user.ID), jsonUser).Err() + err = redisClient.HSet(usersInfo, strconv.Itoa(user.ID), jsonUser).Err() if err != nil { log.Printf("Error adding user info in hash: %v", err) return ErrRedisAddHash @@ -35,7 +48,7 @@ func isUser(userID int) (bool, error) { if redisClient == nil { return false, ErrNilPointer } - user, err := redisClient.SIsMember(userSet, strconv.Itoa(userID)).Result() + user, err := redisClient.SIsMember(usersID, strconv.Itoa(userID)).Result() if err != nil { log.Printf("Error checking if ID is bot user: %v", err) return false, ErrRedisCheckSet @@ -47,7 +60,7 @@ func getUserInfo(userID int) (*tb.User, error) { if redisClient == nil { return nil, ErrNilPointer } - user, err := redisClient.HGet(userHash, strconv.Itoa(userID)).Result() + user, err := redisClient.HGet(usersInfo, strconv.Itoa(userID)).Result() if err != nil { log.Printf("Error retriving user info from hash: %v", err) return nil, ErrRedisRetrieveHash @@ -65,7 +78,7 @@ func isAuthrizedUser(userID int) (bool, error) { if redisClient == nil { return false, ErrNilPointer } - auth, err := redisClient.SIsMember(authUserSet, strconv.Itoa(userID)).Result() + auth, err := redisClient.SIsMember(authUsers, strconv.Itoa(userID)).Result() if err != nil { log.Printf("Error checking if user is authorized: %v", err) return false, ErrRedisCheckSet @@ -78,13 +91,13 @@ func authorizeUser(userID int, authorized bool) error { return ErrNilPointer } if authorized { - err := redisClient.SAdd(authUserSet, strconv.Itoa(userID)).Err() + err := redisClient.SAdd(authUsers, strconv.Itoa(userID)).Err() if err != nil { log.Printf("Error adding token to set: %v", err) return ErrRedisAddSet } } else { - err := redisClient.SRem(authUserSet, strconv.Itoa(userID)).Err() + err := redisClient.SRem(authUsers, strconv.Itoa(userID)).Err() if err != nil { log.Printf("Error removing token from set: %v", err) return ErrRedisRemSet @@ -92,3 +105,43 @@ func authorizeUser(userID int, authorized bool) error { } return nil } + +func setUserGroups(userID int, groups ...userGroup) error { + if redisClient == nil { + return ErrNilPointer + } + var csvGroups string + for _, group := range groups { + csvGroups += strconv.Itoa(int(group)) + "," + } + err := redisClient.HSet(usersGroups, strconv.Itoa(userID), csvGroups).Err() + if err != nil { + log.Printf("Error adding user groups to hash: %v", err) + return ErrRedisAddHash + } + + return nil +} + +func getUserGroups(userID int) ([]userGroup, error) { + if redisClient == nil { + return nil, ErrNilPointer + } + + csvGroups, err := redisClient.HGet(usersGroups, strconv.Itoa(userID)).Result() + if err != nil { + log.Printf("Error retrieving user groups: %v", err) + return nil, ErrRedisRetrieveHash + } + var retGroups []userGroup + groups := strings.Split(csvGroups, ",") + for _, group := range groups { + intGroup, err := strconv.Atoi(group) + if err != nil { + log.Printf("Error converting user group: %v", err) + return nil, ErrAtoiConv + } + retGroups = append(retGroups, userGroup(intGroup)) + } + return retGroups, nil +} diff --git a/src/redisAPI.go b/src/redisAPI.go index fe427e3..f77ed54 100644 --- a/src/redisAPI.go +++ b/src/redisAPI.go @@ -9,11 +9,12 @@ import ( const ( botToken = "botToken" - botHash = "botInfo" - userSet = "userID" - userHash = "userInfo" - authUserSet = "authUser" - adminSet = "adminID" + botInfo = "botInfo" + usersID = "usersID" + usersInfo = "usersInfo" + usersGroups = "usersGroups" + authUsers = "authUsers" + adminUsers = "adminUsers" ) var redisClient *redis.Client diff --git a/src/sys.go b/src/sys.go index eb2226c..493990d 100644 --- a/src/sys.go +++ b/src/sys.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "log" + "os" "strings" "github.com/dixonwille/wmenu" @@ -23,7 +24,7 @@ type flags struct { var cmdFlags flags var ( - welcomeMessage = "Welcome in barandaBot! Here you can control the bot(s) options and configurations." + welcomeMessage = "Welcome! Here you can control the bot options and configurations." //ErrStdRead is thrown when it's not possible to read from the standard input ErrStdRead = errors.New("stdin: couldn't read string from stdin") //ErrMainMenu is thrown when a menu couldn't be started @@ -80,31 +81,62 @@ func getFlags() error { return nil } -func mainMenu() error { - fmt.Println(welcomeMessage) +func exit() error { + if isStartedBot { + log.Printf("Stopping %s", bot.Me.Username) + bot.Stop() + log.Println("Bot stopped") + } + log.Println("Closing redis instance") + redisClient.Close() + log.Println("Redis instance closed") + log.Println("Exiting") + os.Exit(0) + + return nil +} + +func mainMenu() *wmenu.Menu { menu := wmenu.NewMenu("What do you want to do?") menu.LoopOnInvalid() - menu.Option("Start Bot", nil, true, func(opt wmenu.Opt) error { - return botStart() - }) - menu.Option("Set bot token", nil, false, func(opt wmenu.Opt) error { - return setBotToken("") - }) + if !isStartedBot { + menu.Option("Start bot", nil, true, func(opt wmenu.Opt) error { + return botStart() + }) + menu.Option("Set bot token", nil, false, func(opt wmenu.Opt) error { + return setBotToken("") + }) + } + if isStartedBot { + menu.Option("Stop bot", nil, true, func(opt wmenu.Opt) error { + return botStop() + }) + } 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() }) + menu.Option("Exit", nil, false, func(opt wmenu.Opt) error { + return exit() + }) - var returnErr error + return menu +} +func mainMenuLoop() error { + menu := mainMenu() + botStatus := isStartedBot + fmt.Println(welcomeMessage) for { err := menu.Run() if err != nil { log.Printf("Error in main menu: %v", err) - returnErr = ErrMainMenu + } + if botStatus != isStartedBot { + botStatus = isStartedBot + menu = mainMenu() } } - return returnErr } diff --git a/src/telegramAPI.go b/src/telegramAPI.go index dc91318..303c0b4 100644 --- a/src/telegramAPI.go +++ b/src/telegramAPI.go @@ -9,7 +9,13 @@ import ( tb "gopkg.in/tucnak/telebot.v2" ) +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." +) + var bot *tb.Bot +var isStartedBot bool var ( //ErrNilPointer is thrown when a pointer is nil @@ -18,15 +24,21 @@ var ( ErrIDFromMsg = errors.New("telegram: couldn't retrieve user ID from message") //ErrSendMsg is thrown when the message couldn't be send ErrSendMsg = errors.New("telegram: cannot send message") - //ErrChatRetrive is thrown when the chat cannot be retrieved + //ErrChatRetrieve is thrown when the chat cannot be retrieved ErrChatRetrieve = errors.New("telegram: cannot retrieve chat") + //ErrTokenMissing is thrown when neither a token is in the db nor one is passed with -t on program start + ErrTokenMissing = errors.New("telegram: cannot start bot without a token") + //ErrBotInit is thrown when a bot couldn't be initialized + 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") ) func botInit() error { token, err := getBotToken() if err != nil { log.Printf("Error in retriving bot token: %v. Cannot start telebot without token.", err) - return err + return ErrTokenMissing } poller := &tb.LongPoller{Timeout: 15 * time.Second} @@ -61,15 +73,22 @@ func botInit() error { Token: token, Poller: middlePoller, }) - if err != nil { log.Printf("Error in enstablishing connection for bot %s: %v", bot.Me.Username, err) - } else { - err = addBotInfo(token, bot) - if err != nil { - log.Printf("Error: bot %s info couldn't be added: %v", bot.Me.Username, err) - } + return ErrBotConn } + + err = setBotHandlers() + if err != nil { + log.Printf("Error setting bot handlers: %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 nil } @@ -82,11 +101,11 @@ func sendMessage(user *tb.User, msg string) error { return nil } -func botStart() error { +func setBotHandlers() error { if bot == nil { return ErrNilPointer } - log.Printf("Started %s", bot.Me.Username) + bot.Handle("/hello", func(m *tb.Message) { bot.Send(m.Sender, "hello world") }) @@ -94,7 +113,29 @@ func botStart() error { bot.Send(m.Sender, strconv.Itoa(m.Sender.ID)) }) - bot.Start() + return nil +} + +func botStart() error { + if bot == nil { + return ErrNilPointer + } + + go bot.Start() + isStartedBot = true + log.Printf("Started %s", bot.Me.Username) + + return nil +} + +func botStop() error { + if bot == nil { + return ErrNilPointer + } + log.Printf("Stopping %s", bot.Me.Username) + bot.Stop() + isStartedBot = false + log.Println("Bot stopped") return nil }