501 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			501 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"encoding/hex"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| 
 | |
| 	tb "gopkg.in/tucnak/telebot.v2"
 | |
| )
 | |
| 
 | |
| type Bot struct {
 | |
| 	bot    *tb.Bot
 | |
| 	Config *TelegramConfig
 | |
| }
 | |
| 
 | |
| type CompanyDeleteEntry struct {
 | |
| 	CompanyID uint8
 | |
| 	UserID    int
 | |
| 	Time      time.Time
 | |
| }
 | |
| 
 | |
| var companyDeleteMap map[string]*CompanyDeleteEntry
 | |
| 
 | |
| func (b *Bot) Start() {
 | |
| 	var err error
 | |
| 	b.bot, err = tb.NewBot(tb.Settings{
 | |
| 		Token:  b.Config.Token,
 | |
| 		URL:    b.Config.URL,
 | |
| 		Poller: &tb.LongPoller{Timeout: 10 * time.Second},
 | |
| 	})
 | |
| 	failError(err, "Bot.Start() : registering bot")
 | |
| 
 | |
| 	companyDeleteMap = make(map[string]*CompanyDeleteEntry)
 | |
| 
 | |
| 	b.BotHandlers()
 | |
| }
 | |
| 
 | |
| func (b *Bot) SendUser(id int64, msg string) {
 | |
| 	u := tb.User{
 | |
| 		ID: int(id),
 | |
| 	}
 | |
| 	_, err := b.bot.Send(&u, msg)
 | |
| 	logErrorDebug(err, "Bot.SendUser()")
 | |
| }
 | |
| 
 | |
| func (b *Bot) SendChat(chatID int64, text string) {
 | |
| 	opt := tb.SendOptions{
 | |
| 		ParseMode: tb.ModeDefault,
 | |
| 	}
 | |
| 
 | |
| 	ch := tb.Chat{
 | |
| 		ID: chatID,
 | |
| 	}
 | |
| 	_, err := b.bot.Send(&ch, text, &opt)
 | |
| 	logErrorDebug(err, "Bot.SendChat()")
 | |
| }
 | |
| 
 | |
| func (b *Bot) BotHandlers() {
 | |
| 
 | |
| 	b.bot.Handle("/pause", botPause)
 | |
| 	b.bot.Handle("/unpause", botUnpause)
 | |
| 	b.bot.Handle("/register", botRegister)
 | |
| 	b.bot.Handle("/deregister", botDeregister)
 | |
| 	b.bot.Handle("/delete", botDelete)
 | |
| 	b.bot.Handle("/companies", botCompanies)
 | |
| 	b.bot.Handle("/clients", botClients)
 | |
| 	b.bot.Handle("/players", botPlayers)
 | |
| 	b.bot.Handle("/give", botGive)
 | |
| 	b.bot.Handle("/take", botTake)
 | |
| 	b.bot.Handle("/passwd", botPasswd)
 | |
| 
 | |
| 	b.bot.Handle(tb.OnPhoto, botPhoto)
 | |
| 	b.bot.Handle(tb.OnChannelPost, botChannelPost)
 | |
| 	b.bot.Handle(tb.OnQuery, botQuery)
 | |
| 	b.bot.Handle(tb.OnText, botText)
 | |
| 	b.bot.Handle(tb.OnDocument, botDocument)
 | |
| 
 | |
| 	go func() {
 | |
| 		time.Sleep(time.Second)
 | |
| 		b.SendUser(b.Config.AdminID, fmt.Sprintf("Started (%s)", version))
 | |
| 	}()
 | |
| 
 | |
| 	b.bot.Start()
 | |
| }
 | |
| 
 | |
| func botPause(m *tb.Message) {
 | |
| 	for userID, cc := range cfg.Clients {
 | |
| 		if userID == m.Sender.ID {
 | |
| 			if co, ok := srv.Status.Companies[cc.CompanyID]; ok {
 | |
| 				if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 {
 | |
| 					clt.Paused = true
 | |
| 					if !srv.Status.Paused {
 | |
| 						srv.Pause()
 | |
| 					} else {
 | |
| 						bot.SendChat(bot.Config.ChatID, "Game already paused.")
 | |
| 					}
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	bot.SendChat(bot.Config.ChatID, "You are not playing and cannot pause the game.")
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botUnpause(m *tb.Message) {
 | |
| 	PrintText(m)
 | |
| 	for userID, cc := range cfg.Clients {
 | |
| 		if userID == m.Sender.ID {
 | |
| 			if co, ok := srv.Status.Companies[cc.CompanyID]; ok {
 | |
| 				if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 {
 | |
| 					clt.Paused = false
 | |
| 					if !srv.NeedPause() {
 | |
| 						srv.Unpause()
 | |
| 					} else {
 | |
| 						bot.SendChat(bot.Config.ChatID, fmt.Sprintf("Cannot unpause : %s", srv.NeedPauseReason()))
 | |
| 					}
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	bot.SendChat(bot.Config.ChatID, "You are not playing and cannot unpause the game.")
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botDelete(m *tb.Message) {
 | |
| 	logInfoDebug("[%d] %s(%d) | %s(%d) : delete : %s\n", m.ID, m.Chat.Title, m.Chat.ID, m.Sender.Username, m.Sender.ID, m.Text)
 | |
| 	r := regexp.MustCompile("/delete (?P<CompanyID>[0-9]+)")
 | |
| 	id := uint8(255)
 | |
| 	if r.MatchString(m.Text) {
 | |
| 		ID64, _ := strconv.ParseInt(r.ReplaceAllString(m.Text, "${CompanyID}"), 10, 64)
 | |
| 		if m.Sender.ID == int(bot.Config.AdminID) {
 | |
| 			id = uint8(ID64)
 | |
| 		} else if cc, ok := cfg.Clients[m.Sender.ID]; ok && cc.CompanyID == uint8(ID64) {
 | |
| 			id = cc.CompanyID
 | |
| 		} else {
 | |
| 			bot.SendChat(m.Chat.ID, "Not authorized to delete")
 | |
| 			return
 | |
| 		}
 | |
| 	} else if cc, ok := cfg.Clients[m.Sender.ID]; ok {
 | |
| 		id = cc.CompanyID
 | |
| 	} else {
 | |
| 		bot.SendChat(m.Chat.ID, "User not registered")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if id == 255 {
 | |
| 		bot.SendChat(m.Chat.ID, "No company registered")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if co, ok := srv.Status.Companies[id]; ok {
 | |
| 		b := make([]byte, 8)
 | |
| 		_, err := rand.Read(b)
 | |
| 		logErrorDebug(err, "botDelete : rand.Read")
 | |
| 		if err != nil {
 | |
| 			bot.SendChat(m.Chat.ID, "internal error")
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		c := hex.EncodeToString(b)
 | |
| 		d := &CompanyDeleteEntry{
 | |
| 			CompanyID: id,
 | |
| 			UserID:    m.Sender.ID,
 | |
| 			Time:      time.Now(),
 | |
| 		}
 | |
| 		companyDeleteMap[c] = d
 | |
| 		bot.SendChat(m.Chat.ID, fmt.Sprintf("Press /delete_%s to delete '%s'", c, co.Name))
 | |
| 
 | |
| 	} else {
 | |
| 		bot.SendChat(m.Chat.ID, "Company doesn't exist")
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botCompanies(m *tb.Message) {
 | |
| 	str := "Companies :"
 | |
| 	for k, v := range srv.Status.Companies {
 | |
| 		str = str + "\r\n" + fmt.Sprintf(" - %s (%d - %t)", v.Name, k, v.Protected)
 | |
| 	}
 | |
| 	bot.SendChat(m.Chat.ID, str)
 | |
| }
 | |
| 
 | |
| func botClients(m *tb.Message) {
 | |
| 	str := "Clients :"
 | |
| 	for k, v := range srv.Status.Clients {
 | |
| 		str = str + "\r\n" + fmt.Sprintf(" - %s (%d) : company #%d", v.Name, k, v.CompanyID)
 | |
| 	}
 | |
| 	bot.SendChat(m.Chat.ID, str)
 | |
| }
 | |
| 
 | |
| func botPlayers(m *tb.Message) {
 | |
| 	d1 := time.Now().Sub(cfg.Game.StartDate)
 | |
| 	days := int(time.Duration(d1.Hours()) / 24)
 | |
| 	d2 := time.Duration(days+1)*(time.Hour)*24 - d1
 | |
| 	str := fmt.Sprintf("Update in %s\r\n", d2)
 | |
| 
 | |
| 	online := ""
 | |
| 	for _, cc := range cfg.Clients {
 | |
| 		if cc.Online {
 | |
| 			online = online + fmt.Sprintf(" - %s (%s) : %s", cc.Username, cc.TimeLeft, srv.Status.Companies[cc.CompanyID].Name) + "\r\n"
 | |
| 		}
 | |
| 	}
 | |
| 	offline := ""
 | |
| 	for _, cc := range cfg.Clients {
 | |
| 		if _, ok := srv.Status.Companies[cc.CompanyID]; ok && !cc.Online {
 | |
| 			offline = offline + fmt.Sprintf(" - %s (%s) : %s", cc.Username, cc.TimeLeft, srv.Status.Companies[cc.CompanyID].Name) + "\r\n"
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(online) > 0 {
 | |
| 		str = str + "Players online :\r\n" + online
 | |
| 	}
 | |
| 	if len(offline) > 0 {
 | |
| 		str = str + "Players offline :\r\n" + offline
 | |
| 	}
 | |
| 
 | |
| 	bot.SendChat(m.Chat.ID, str)
 | |
| }
 | |
| 
 | |
| func botGive(m *tb.Message) {
 | |
| 	if m.Sender.ID != int(cfg.Telegram.AdminID) {
 | |
| 		bot.SendChat(m.Chat.ID, "Only the admin can use this command.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	r := regexp.MustCompile("^/give @(?P<Username>[a-zA-Z0-9]+) (?P<Duration>[a-z0-9]+)")
 | |
| 
 | |
| 	if !r.MatchString(m.Text) {
 | |
| 		bot.SendChat(m.Chat.ID, "Wrong format.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	uStr := r.ReplaceAllString(m.Text, "${Username}")
 | |
| 	var u int
 | |
| 	for ccID, cc := range cfg.Clients {
 | |
| 		if cc.Username == uStr {
 | |
| 			u = ccID
 | |
| 		}
 | |
| 	}
 | |
| 	if u == 0 {
 | |
| 		bot.SendChat(m.Chat.ID, "No such user found.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	dStr := r.ReplaceAllString(m.Text, "${Duration}")
 | |
| 	d, err := time.ParseDuration(dStr)
 | |
| 	logErrorDebug(err, "botGive : time.ParseDuration")
 | |
| 	if err != nil {
 | |
| 		bot.SendChat(m.Chat.ID, "Cannot parse duration.")
 | |
| 	}
 | |
| 
 | |
| 	cc := cfg.Clients[u]
 | |
| 	cc.TimeLeft += d
 | |
| 
 | |
| 	bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s now has %s left.", uStr, cc.TimeLeft))
 | |
| }
 | |
| 
 | |
| func botTake(m *tb.Message) {
 | |
| 	if m.Sender.ID != int(cfg.Telegram.AdminID) {
 | |
| 		bot.SendChat(m.Chat.ID, "Only the admin can use this command.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	r := regexp.MustCompile("^/give @(?P<Username>[a-zA-Z0-9]+) (?P<Duration>[a-z0-9]+)")
 | |
| 
 | |
| 	if !r.MatchString(m.Text) {
 | |
| 		bot.SendChat(m.Chat.ID, "Wrong format.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	uStr := r.ReplaceAllString(m.Text, "${Username}")
 | |
| 	var u int
 | |
| 	for ccID, cc := range cfg.Clients {
 | |
| 		if cc.Username == uStr {
 | |
| 			u = ccID
 | |
| 		}
 | |
| 	}
 | |
| 	if u == 0 {
 | |
| 		bot.SendChat(m.Chat.ID, "No such user found.")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	dStr := r.ReplaceAllString(m.Text, "${Duration}")
 | |
| 	d, err := time.ParseDuration(dStr)
 | |
| 	logErrorDebug(err, "botGive : time.ParseDuration")
 | |
| 	if err != nil {
 | |
| 		bot.SendChat(m.Chat.ID, "Cannot parse duration.")
 | |
| 	}
 | |
| 
 | |
| 	cc := cfg.Clients[u]
 | |
| 	cc.TimeLeft -= d
 | |
| 
 | |
| 	bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s now has %s left.", uStr, cc.TimeLeft))
 | |
| }
 | |
| 
 | |
| func botPasswd(m *tb.Message) {
 | |
| 	cc, ok := cfg.Clients[m.Sender.ID]
 | |
| 	if !ok {
 | |
| 		bot.SendChat(m.Chat.ID, "User not registered.")
 | |
| 		return
 | |
| 	}
 | |
| 	if cc.CompanyID == 255 {
 | |
| 		bot.SendChat(m.Chat.ID, "No company registered.")
 | |
| 		return
 | |
| 	}
 | |
| 	co, ok := srv.Status.Companies[cc.CompanyID]
 | |
| 	if !ok {
 | |
| 		bot.SendChat(m.Chat.ID, "Registered company doesn't exist.")
 | |
| 		cc.CompanyID = 255
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	r := regexp.MustCompile("^\\/passwd( )+(?P<Passwd>[^ ]+)$")
 | |
| 	if !r.MatchString(m.Text) {
 | |
| 		bot.SendChat(m.Chat.ID, "No passwd provided")
 | |
| 		return
 | |
| 	}
 | |
| 	// we have a parameter
 | |
| 	cc.Passwd = r.ReplaceAllString(m.Text, "${Passwd}")
 | |
| 
 | |
| 	err := bot.bot.Delete(m)
 | |
| 	logErrorDebug(err, "botPasswd : Delete")
 | |
| 
 | |
| 	srv.SetPasswd(co.CompanyExtlID, cc.Passwd)
 | |
| 
 | |
| 	bot.SendUser(int64(m.Sender.ID), fmt.Sprintf("Passwd set to \"%s\"", cc.Passwd))
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botDeregister(m *tb.Message) {
 | |
| 	cc, ok := cfg.Clients[m.Sender.ID]
 | |
| 	if !ok {
 | |
| 		cc = &ClientConfig{
 | |
| 			UserID:    m.Sender.ID,
 | |
| 			Username:  m.Sender.Username,
 | |
| 			CompanyID: 255,
 | |
| 			TimeLeft:  0,
 | |
| 		}
 | |
| 		cfg.Clients[m.Sender.ID] = cc
 | |
| 		bot.SendChat(m.Chat.ID, "User isn't registered.")
 | |
| 		return
 | |
| 	}
 | |
| 	if cc.CompanyID != 255 {
 | |
| 		for coID, co := range srv.Status.Companies {
 | |
| 			if coID == cc.CompanyID {
 | |
| 				cc.CompanyID = 255
 | |
| 				bot.SendChat(m.Chat.ID, fmt.Sprintf("Deregistered from %s. %s playable left.", co.Name, cc.TimeLeft))
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 		logInfoAlert("botRegister : %s : no such CompanyID : %d", cc.Username, cc.CompanyID)
 | |
| 		cc.CompanyID = 255
 | |
| 		bot.SendChat(m.Chat.ID, fmt.Sprintf("Registered company didn't exist anymore. %s playable left.", cc.TimeLeft))
 | |
| 		return
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botRegister(m *tb.Message) {
 | |
| 	cc, ok := cfg.Clients[m.Sender.ID]
 | |
| 	if !ok {
 | |
| 		cc = &ClientConfig{
 | |
| 			UserID:    m.Sender.ID,
 | |
| 			Username:  m.Sender.Username,
 | |
| 			CompanyID: 255,
 | |
| 			TimeLeft:  0,
 | |
| 		}
 | |
| 		cfg.Clients[m.Sender.ID] = cc
 | |
| 	} else {
 | |
| 		if cc.CompanyID != 255 {
 | |
| 			for coID, co := range srv.Status.Companies {
 | |
| 				if coID == cc.CompanyID {
 | |
| 					bot.SendChat(m.Chat.ID, fmt.Sprintf("Already registered %s. Please /deregister first.", co.Name))
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 			logInfoAlert("botRegister : %s : no such CompanyID : %d", cc.Username, cc.CompanyID)
 | |
| 			cc.CompanyID = 255
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	coList := make(map[uint8]struct{})
 | |
| 	for coID, _ := range srv.Status.Companies {
 | |
| 		coList[coID] = struct{}{}
 | |
| 	}
 | |
| 	for _, c := range cfg.Clients {
 | |
| 		if c.CompanyID != 255 {
 | |
| 			if _, ok := coList[c.CompanyID]; !ok {
 | |
| 				logInfoAlert("botRegister : %s : no such CompanyID : %d", c.Username, c.CompanyID)
 | |
| 				c.CompanyID = 255
 | |
| 			} else {
 | |
| 				delete(coList, c.CompanyID)
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(coList) == 0 {
 | |
| 		bot.SendChat(m.Chat.ID, "No company to register")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	r := regexp.MustCompile("^\\/register( )+(?P<Company>[a-zA-Z\\.\\-0-9 ]+)$")
 | |
| 	if r.MatchString(m.Text) {
 | |
| 		// we have a parameter
 | |
| 		coName := r.ReplaceAllString(m.Text, "${Company}")
 | |
| 		for coID, co := range srv.Status.Companies {
 | |
| 			if co.Name == coName {
 | |
| 				for _, c := range cfg.Clients {
 | |
| 					if c.CompanyID == coID {
 | |
| 						bot.SendChat(m.Chat.ID, fmt.Sprintf("Company %s is already registered to @%s", coName, c.Username))
 | |
| 						return
 | |
| 					}
 | |
| 				}
 | |
| 				cc.CompanyID = coID
 | |
| 				if cc.TimeLeft == 0 {
 | |
| 					days := int(time.Now().Sub(cfg.Game.StartDate).Hours() / 24)
 | |
| 					cc.TimeLeft = cfg.Game.StartingAllotment + cfg.Game.DailyAllotment*time.Duration(days)
 | |
| 				}
 | |
| 				bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s (with %s playable)", cc.Username, srv.Status.Companies[cc.CompanyID].Name, cc.TimeLeft))
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 		bot.SendChat(m.Chat.ID, fmt.Sprintf("Can't find company %s", coName))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if len(coList) == 1 {
 | |
| 		for id, _ := range coList {
 | |
| 			cc.CompanyID = id
 | |
| 			if cc.TimeLeft == 0 {
 | |
| 				days := int(time.Now().Sub(cfg.Game.StartDate).Hours() / 24)
 | |
| 				cc.TimeLeft = cfg.Game.StartingAllotment + cfg.Game.DailyAllotment*time.Duration(days)
 | |
| 			}
 | |
| 			bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s (with %s playable)", cc.Username, srv.Status.Companies[cc.CompanyID].Name, cc.TimeLeft))
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	bot.SendChat(m.Chat.ID, "More than one company unregistered. Wait for bot update (poke @tiennou)")
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func PrintText(m *tb.Message) {
 | |
| 	logInfoDebug("[%d] %s(%d) | %s(%d) : %s\n", m.ID, m.Chat.Title, m.Chat.ID, m.Sender.Username, m.Sender.ID, m.Text)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func botPhoto(m *tb.Message) {
 | |
| 	logInfoDebug("botPhoto :", m.Text)
 | |
| 	//	photos only
 | |
| }
 | |
| 
 | |
| func botDocument(m *tb.Message) {
 | |
| 	logInfoDebug("botDocument : %s (%d bytes)\n", m.Document.FileName, m.Document.File.FileSize)
 | |
| 	//	documents only
 | |
| }
 | |
| 
 | |
| func botChannelPost(m *tb.Message) {
 | |
| 	PrintText(m)
 | |
| 	b, _ := json.Marshal(m)
 | |
| 	logInfoDebug("botChannelPost : %s\n", string(b))
 | |
| 	//	channel posts only
 | |
| }
 | |
| 
 | |
| func botQuery(q *tb.Query) {
 | |
| 	logInfoDebug("botQuery")
 | |
| 	//	incoming inline queries
 | |
| }
 | |
| 
 | |
| func botText(m *tb.Message) {
 | |
| 	r := regexp.MustCompile("^/delete_(??<Ref>[a-f0-9]{16})$")
 | |
| 	if r.MatchString(m.Text) {
 | |
| 		ref := r.ReplaceAllString(m.Text, "${Ref}")
 | |
| 		d, ok := companyDeleteMap[ref]
 | |
| 		if !ok {
 | |
| 			bot.SendChat(m.Chat.ID, "No corresponding deletion request.")
 | |
| 			return
 | |
| 		}
 | |
| 		if d.UserID != m.Sender.ID {
 | |
| 			bot.SendChat(m.Chat.ID, "Requesting user has to confirm himself.")
 | |
| 			return
 | |
| 		}
 | |
| 		if time.Now().Sub(d.Time) > time.Minute {
 | |
| 			bot.SendChat(m.Chat.ID, "Request expired.")
 | |
| 			return
 | |
| 		}
 | |
| 		srv.DeleteCompany(d.CompanyID)
 | |
| 		bot.SendChat(m.Chat.ID, "Company deleted.")
 | |
| 		delete(companyDeleteMap, ref)
 | |
| 		return
 | |
| 	}
 | |
| 	PrintText(m)
 | |
| }
 |