sco/bot/bot.go

360 lines
9.8 KiB
Go
Raw Normal View History

2020-09-09 01:14:51 +00:00
package bot
2020-09-08 22:58:55 +00:00
2020-09-09 04:13:53 +00:00
//import "github.com/kr/pretty"
2020-09-08 22:58:55 +00:00
import (
2020-09-09 02:15:17 +00:00
"fmt"
2020-09-08 22:58:55 +00:00
"os"
"os/signal"
"regexp"
"strings"
2020-09-09 02:15:17 +00:00
"syscall"
"time"
2020-09-09 04:15:50 +00:00
"github.com/mattermost/mattermost-server/v5/model"
"github.com/rs/zerolog/log"
2020-09-08 22:58:55 +00:00
)
type Bot struct {
2020-09-09 01:14:51 +00:00
APIURL string
AccountEmail string
AccountFirstname string
AccountLastname string
AccountPassword string
AccountUsername string
BotName string
Buildarch string
DebuggingChannelName string
TeamName string
Version string
2020-09-09 02:57:28 +00:00
Commit string
2020-09-09 01:14:51 +00:00
WebsocketURL string
2020-09-09 02:15:17 +00:00
StartupUnixTime int64
2020-09-08 23:13:15 +00:00
client *model.Client4
webSocketClient *model.WebSocketClient
botUser *model.User
botTeam *model.Team
debuggingChannel *model.Channel
2020-09-08 22:58:55 +00:00
}
func New(options ...func(s *Bot)) *Bot {
b := new(Bot)
for _, opt := range options {
opt(b)
}
return b
}
2020-09-09 03:56:24 +00:00
func (b *Bot) identify() {
log.Info().
Str("version", b.Version).
Str("buildarch", b.Buildarch).
Str("commit", b.Commit).
Msg("starting")
}
2020-09-09 04:21:13 +00:00
func (b *Bot) Main() {
2020-09-09 01:14:51 +00:00
println(b.BotName)
2020-09-08 22:58:55 +00:00
2020-09-09 02:15:17 +00:00
b.StartupUnixTime = time.Now().Unix()
2020-09-08 22:58:55 +00:00
b.SetupGracefulShutdown()
2020-09-09 03:56:24 +00:00
b.setupLogging()
2020-09-09 01:14:51 +00:00
b.client = model.NewAPIv4Client(b.APIURL)
2020-09-08 22:58:55 +00:00
// Lets test to see if the mattermost server is up and running
b.MakeSureServerIsRunning()
// lets attempt to login to the Mattermost server as the bot user
// This will set the token required for all future calls
// You can get this token with client.AuthToken
b.LoginAsTheBotUser()
// If the bot user doesn't have the correct information lets update his profile
b.UpdateTheBotUserIfNeeded()
// Lets find our bot team
b.FindBotTeam()
// This is an important step. Lets make sure we use the botTeam
// for all future web service requests that require a team.
//client.SetTeamId(botTeam.Id)
// Lets create a bot channel for logging debug messages into
b.CreateBotDebuggingChannelIfNeeded()
2020-09-09 03:37:50 +00:00
msg := fmt.Sprintf("_**%s** (version `%s`) is now starting up_", b.BotName, b.Version)
2020-09-09 02:57:28 +00:00
b.SendMsgToDebuggingChannel(msg, "")
2020-09-08 22:58:55 +00:00
// Lets start listening to some channels via the websocket!
2020-09-08 23:13:15 +00:00
var err *model.AppError
2020-09-09 01:14:51 +00:00
b.webSocketClient, err = model.NewWebSocketClient4(b.WebsocketURL, b.client.AuthToken)
2020-09-08 22:58:55 +00:00
if err != nil {
println("We failed to connect to the web socket")
PrintError(err)
}
b.webSocketClient.Listen()
go func() {
for {
select {
case resp := <-b.webSocketClient.EventChannel:
b.HandleWebSocketResponse(resp)
}
}
}()
// You can block forever with
select {}
}
func (b *Bot) MakeSureServerIsRunning() {
2020-09-08 23:13:15 +00:00
if props, resp := b.client.GetOldClientConfig(""); resp.Error != nil {
2020-09-08 22:58:55 +00:00
println("There was a problem pinging the Mattermost server. Are you sure it's running?")
PrintError(resp.Error)
os.Exit(1)
} else {
println("Server detected and is running version " + props["Version"])
}
}
func (b *Bot) LoginAsTheBotUser() {
2020-09-09 01:14:51 +00:00
if user, resp := b.client.Login(b.AccountEmail, b.AccountPassword); resp.Error != nil {
2020-09-08 22:58:55 +00:00
println("There was a problem logging into the Mattermost server. Are you sure ran the setup steps from the README.md?")
PrintError(resp.Error)
os.Exit(1)
} else {
2020-09-08 23:13:15 +00:00
b.botUser = user
2020-09-08 22:58:55 +00:00
}
}
func (b *Bot) UpdateTheBotUserIfNeeded() {
2020-09-09 01:14:51 +00:00
if b.botUser.FirstName != b.AccountFirstname || b.botUser.LastName != b.AccountLastname || b.botUser.Username != b.AccountUsername {
b.botUser.FirstName = b.AccountFirstname
b.botUser.LastName = b.AccountLastname
b.botUser.Username = b.AccountUsername
2020-09-08 22:58:55 +00:00
2020-09-08 23:13:15 +00:00
if user, resp := b.client.UpdateUser(b.botUser); resp.Error != nil {
println("We failed to update the Bot user account")
2020-09-08 22:58:55 +00:00
PrintError(resp.Error)
os.Exit(1)
} else {
2020-09-08 23:13:15 +00:00
b.botUser = user
2020-09-08 22:58:55 +00:00
println("Looks like this might be the first run so we've updated the bots account settings")
}
}
}
func (b *Bot) FindBotTeam() {
2020-09-09 01:14:51 +00:00
if team, resp := b.client.GetTeamByName(b.TeamName, ""); resp.Error != nil {
2020-09-08 22:58:55 +00:00
println("We failed to get the initial load")
2020-09-09 01:14:51 +00:00
println("or we do not appear to be a member of the team '" + b.TeamName + "'")
2020-09-08 22:58:55 +00:00
PrintError(resp.Error)
os.Exit(1)
} else {
2020-09-08 23:13:15 +00:00
b.botTeam = team
2020-09-08 22:58:55 +00:00
}
}
func (b *Bot) CreateBotDebuggingChannelIfNeeded() {
2020-09-09 01:14:51 +00:00
if rchannel, resp := b.client.GetChannelByName(b.DebuggingChannelName, b.botTeam.Id, ""); resp.Error != nil {
2020-09-08 22:58:55 +00:00
println("We failed to get the channels")
PrintError(resp.Error)
} else {
2020-09-08 23:13:15 +00:00
b.debuggingChannel = rchannel
2020-09-08 22:58:55 +00:00
return
}
// Looks like we need to create the logging channel
channel := &model.Channel{}
2020-09-09 01:14:51 +00:00
channel.Name = b.DebuggingChannelName
2020-09-09 02:15:17 +00:00
channel.DisplayName = "LSV Serious Callers Only"
channel.Purpose = "Bot Channel"
2020-09-08 22:58:55 +00:00
channel.Type = model.CHANNEL_OPEN
2020-09-08 23:13:15 +00:00
channel.TeamId = b.botTeam.Id
if rchannel, resp := b.client.CreateChannel(channel); resp.Error != nil {
2020-09-09 01:14:51 +00:00
println("We failed to create the channel " + b.DebuggingChannelName)
2020-09-08 22:58:55 +00:00
PrintError(resp.Error)
} else {
2020-09-08 23:13:15 +00:00
b.debuggingChannel = rchannel
2020-09-09 01:14:51 +00:00
println("Looks like this might be the first run so we've created the channel " + b.DebuggingChannelName)
2020-09-08 22:58:55 +00:00
}
}
func (b *Bot) SendMsgToChannel(msg string, replyToId string, channelId string) {
post := &model.Post{}
2020-09-09 01:55:13 +00:00
post.ChannelId = channelId
post.Message = msg
post.RootId = replyToId
if _, resp := b.client.CreatePost(post); resp.Error != nil {
2020-09-09 02:15:17 +00:00
println("We failed to send a message to the channel")
PrintError(resp.Error)
}
}
2020-09-08 22:58:55 +00:00
func (b *Bot) SendMsgToDebuggingChannel(msg string, replyToId string) {
post := &model.Post{}
2020-09-08 23:13:15 +00:00
post.ChannelId = b.debuggingChannel.Id
2020-09-08 22:58:55 +00:00
post.Message = msg
post.RootId = replyToId
2020-09-08 23:13:15 +00:00
if _, resp := b.client.CreatePost(post); resp.Error != nil {
2020-09-08 22:58:55 +00:00
println("We failed to send a message to the logging channel")
PrintError(resp.Error)
}
}
func (b *Bot) HandleWebSocketResponse(event *model.WebSocketEvent) {
2020-09-09 02:15:17 +00:00
2020-09-09 01:49:54 +00:00
if event.Event != model.WEBSOCKET_EVENT_POSTED {
return
}
post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
if post == nil {
return
}
if post.UserId == b.botUser.Id {
return
}
2020-09-13 07:49:19 +00:00
// FIXME check for parts and joins and whatnot and ignore those
2020-09-09 02:15:17 +00:00
// check to see if we have been addressed
if matched, _ := regexp.MatchString(`^\s*`+b.BotName+`\s*`, post.Message); matched {
println("i have been addressed in channel " + post.ChannelId)
//b.SendMsgToDebuggingChannel("i have been addressed in channel "+post.ChannelId, "")
b.HandleMsgFromChannel(event)
return
}
2020-09-13 07:49:19 +00:00
if matched, _ := regexp.MatchString(`^\s*bot([\,]?)\s*`, post.Message); matched {
println("i have been addressed in channel " + post.ChannelId)
//b.SendMsgToDebuggingChannel("i have been addressed in channel "+post.ChannelId, "")
b.HandleMsgFromChannel(event)
return
}
if event.Broadcast.ChannelId == b.debuggingChannel.Id {
b.HandleMsgFromDebuggingChannel(event)
return
2020-09-09 02:15:17 +00:00
}
2020-09-09 01:49:54 +00:00
2020-09-09 02:15:17 +00:00
}
2020-09-09 02:57:28 +00:00
func (b *Bot) Shutdown() {
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
}
func (b *Bot) HandleMsgFromChannel(event *model.WebSocketEvent) {
2020-09-09 02:15:17 +00:00
post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
if post == nil {
return
}
2020-09-09 05:03:39 +00:00
if matched, _ := regexp.MatchString(`metar\s+[^\s]+`, post.Message); matched {
2020-09-09 03:20:52 +00:00
b.HandleWeatherRequest(post.ChannelId, post.Id, post.Message)
return
}
2020-09-09 01:49:54 +00:00
2020-09-09 05:03:39 +00:00
if matched, _ := regexp.MatchString(`aqi\s+[^\s]+`, post.Message); matched {
b.HandleAirQualityRequest(post.ChannelId, post.Id, post.Message)
return
}
if matched, _ := regexp.MatchString(`(?:^|\W)alive(?:$|\W)`, post.Message); matched {
b.SendMsgToChannel("yes I'm running", post.Id, post.ChannelId)
2020-09-09 01:49:54 +00:00
return
}
if matched, _ := regexp.MatchString(`(?:^|\W)version(?:$|\W)`, post.Message); matched {
2020-09-09 02:57:28 +00:00
msg := fmt.Sprintf("I am running version `%s` from git: https://git.eeqj.de/sneak/sco/src/commit/%s", b.Version, b.Commit)
b.SendMsgToChannel(msg, post.Id, post.ChannelId)
2020-09-09 01:49:54 +00:00
return
}
2020-09-09 02:15:17 +00:00
if matched, _ := regexp.MatchString(`(?:^|\W)uptime(?:$|\W)`, post.Message); matched {
2020-09-09 02:57:28 +00:00
msg := fmt.Sprintf("running: uptime %d seconds", b.Uptime())
b.SendMsgToChannel(msg, post.Id, post.ChannelId)
2020-09-09 02:15:17 +00:00
return
}
b.SendMsgToChannel("I did not understand your command, sorry", post.Id, post.ChannelId)
2020-09-09 01:49:54 +00:00
2020-09-08 22:58:55 +00:00
}
2020-09-09 02:57:28 +00:00
func (b *Bot) Uptime() int64 {
return (time.Now().Unix() - b.StartupUnixTime)
}
2020-09-08 22:58:55 +00:00
func (b *Bot) HandleMsgFromDebuggingChannel(event *model.WebSocketEvent) {
println("responding to debugging channel msg")
post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
2020-09-09 03:59:46 +00:00
if post == nil {
return
}
2020-09-08 22:58:55 +00:00
if post.Message == "" {
// null message, we can probably ignore it.
return
}
2020-09-09 02:15:17 +00:00
2020-09-09 03:59:46 +00:00
if matched, _ := regexp.MatchString(`(?:^|\W)shutdown(?:$|\W)`, post.Message); matched {
b.Shutdown()
return
}
2020-09-08 22:58:55 +00:00
2020-09-09 03:59:46 +00:00
// if you see any word matching 'alive' then respond
if matched, _ := regexp.MatchString(`(?:^|\W)alive(?:$|\W)`, post.Message); matched {
b.SendMsgToDebuggingChannel("Yes I'm running", post.Id)
return
}
2020-09-08 22:58:55 +00:00
2020-09-09 03:59:46 +00:00
// if you see any word matching 'up' then respond
if matched, _ := regexp.MatchString(`(?:^|\W)up(?:$|\W)`, post.Message); matched {
b.SendMsgToDebuggingChannel("Yes I'm running", post.Id)
return
}
2020-09-08 22:58:55 +00:00
2020-09-09 03:59:46 +00:00
// if you see any word matching 'running' then respond
if matched, _ := regexp.MatchString(`(?:^|\W)running(?:$|\W)`, post.Message); matched {
b.SendMsgToDebuggingChannel("Yes I'm running", post.Id)
return
}
// if you see any word matching 'hello' then respond
if matched, _ := regexp.MatchString(`(?:^|\W)hello(?:$|\W)`, post.Message); matched {
b.SendMsgToDebuggingChannel("Yes I'm running", post.Id)
return
2020-09-08 22:58:55 +00:00
}
b.SendMsgToChannel("I did not understand your command, sorry", post.Id, post.ChannelId)
2020-09-08 22:58:55 +00:00
}
func PrintError(err *model.AppError) {
println("\tError Details:")
println("\t\t" + err.Message)
println("\t\t" + err.Id)
println("\t\t" + err.DetailedError)
}
func (b *Bot) SetupGracefulShutdown() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for _ = range c {
2020-09-09 02:57:28 +00:00
msg := fmt.Sprintf("_**%s** (version `%s`) is now shutting down_\n\nuptime was %d secs", b.BotName, b.Version, b.Uptime())
b.SendMsgToDebuggingChannel(msg, "")
2020-09-08 23:13:15 +00:00
if b.webSocketClient != nil {
b.webSocketClient.Close()
2020-09-08 22:58:55 +00:00
}
os.Exit(0)
}
}()
}