364 lines
10 KiB
Go
364 lines
10 KiB
Go
package bot
|
|
|
|
//import "github.com/kr/pretty"
|
|
import (
|
|
"fmt"
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
type Bot struct {
|
|
APIURL string
|
|
AccountEmail string
|
|
AccountFirstname string
|
|
AccountLastname string
|
|
AccountPassword string
|
|
AccountUsername string
|
|
BotName string
|
|
Buildarch string
|
|
DebuggingChannelName string
|
|
TeamName string
|
|
Version string
|
|
Commit string
|
|
WebsocketURL string
|
|
StartupUnixTime int64
|
|
client *model.Client4
|
|
webSocketClient *model.WebSocketClient
|
|
botUser *model.User
|
|
botTeam *model.Team
|
|
debuggingChannel *model.Channel
|
|
}
|
|
|
|
func New(options ...func(s *Bot)) *Bot {
|
|
b := new(Bot)
|
|
for _, opt := range options {
|
|
opt(b)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *Bot) Main() int {
|
|
println(b.BotName)
|
|
|
|
b.StartupUnixTime = time.Now().Unix()
|
|
|
|
b.SetupGracefulShutdown()
|
|
|
|
b.client = model.NewAPIv4Client(b.APIURL)
|
|
|
|
// 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()
|
|
msg := fmt.Sprintf("_**%s** (version `%s`) is now starting up", b.BotName, b.Version)
|
|
b.SendMsgToDebuggingChannel(msg, "")
|
|
|
|
// Lets start listening to some channels via the websocket!
|
|
var err *model.AppError
|
|
b.webSocketClient, err = model.NewWebSocketClient4(b.WebsocketURL, b.client.AuthToken)
|
|
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 {}
|
|
return 0
|
|
}
|
|
|
|
func (b *Bot) MakeSureServerIsRunning() {
|
|
if props, resp := b.client.GetOldClientConfig(""); resp.Error != nil {
|
|
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() {
|
|
if user, resp := b.client.Login(b.AccountEmail, b.AccountPassword); resp.Error != nil {
|
|
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 {
|
|
b.botUser = user
|
|
}
|
|
}
|
|
|
|
func (b *Bot) UpdateTheBotUserIfNeeded() {
|
|
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
|
|
|
|
if user, resp := b.client.UpdateUser(b.botUser); resp.Error != nil {
|
|
println("We failed to update the Bot user account")
|
|
PrintError(resp.Error)
|
|
os.Exit(1)
|
|
} else {
|
|
b.botUser = user
|
|
println("Looks like this might be the first run so we've updated the bots account settings")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Bot) FindBotTeam() {
|
|
if team, resp := b.client.GetTeamByName(b.TeamName, ""); resp.Error != nil {
|
|
println("We failed to get the initial load")
|
|
println("or we do not appear to be a member of the team '" + b.TeamName + "'")
|
|
PrintError(resp.Error)
|
|
os.Exit(1)
|
|
} else {
|
|
b.botTeam = team
|
|
}
|
|
}
|
|
|
|
func (b *Bot) CreateBotDebuggingChannelIfNeeded() {
|
|
if rchannel, resp := b.client.GetChannelByName(b.DebuggingChannelName, b.botTeam.Id, ""); resp.Error != nil {
|
|
println("We failed to get the channels")
|
|
PrintError(resp.Error)
|
|
} else {
|
|
b.debuggingChannel = rchannel
|
|
return
|
|
}
|
|
|
|
// Looks like we need to create the logging channel
|
|
channel := &model.Channel{}
|
|
channel.Name = b.DebuggingChannelName
|
|
channel.DisplayName = "LSV Serious Callers Only"
|
|
channel.Purpose = "Bot Channel"
|
|
channel.Type = model.CHANNEL_OPEN
|
|
channel.TeamId = b.botTeam.Id
|
|
if rchannel, resp := b.client.CreateChannel(channel); resp.Error != nil {
|
|
println("We failed to create the channel " + b.DebuggingChannelName)
|
|
PrintError(resp.Error)
|
|
} else {
|
|
b.debuggingChannel = rchannel
|
|
println("Looks like this might be the first run so we've created the channel " + b.DebuggingChannelName)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) SendMsgToChannel(msg string, replyToId string, channelId string) {
|
|
post := &model.Post{}
|
|
post.ChannelId = channelId
|
|
post.Message = msg
|
|
post.RootId = replyToId
|
|
if _, resp := b.client.CreatePost(post); resp.Error != nil {
|
|
println("We failed to send a message to the channel")
|
|
PrintError(resp.Error)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) SendMsgToDebuggingChannel(msg string, replyToId string) {
|
|
post := &model.Post{}
|
|
post.ChannelId = b.debuggingChannel.Id
|
|
post.Message = msg
|
|
|
|
post.RootId = replyToId
|
|
|
|
if _, resp := b.client.CreatePost(post); resp.Error != nil {
|
|
println("We failed to send a message to the logging channel")
|
|
PrintError(resp.Error)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) HandleWebSocketResponse(event *model.WebSocketEvent) {
|
|
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
if event.Broadcast.ChannelId == b.debuggingChannel.Id {
|
|
b.HandleMsgFromDebuggingChannel(event)
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
func (b *Bot) Shutdown() {
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
|
}
|
|
|
|
func (b *Bot) HandleWeatherRequest(channelid string, postid string, message string) {
|
|
msg := fmt.Sprintf("weather request received: `%s`", message)
|
|
b.SendMsgToChannel(msg, postid, channelid)
|
|
|
|
r := regexp.MustCompile(`metar\s+([A-Za-z]{4})`)
|
|
loc := r.FindString(message)
|
|
if loc == "" {
|
|
b.SendMsgToChannel("error, sorry", postid, channelid)
|
|
}
|
|
|
|
token := os.Getenv("METAR_API_TOKEN")
|
|
url := fmt.Sprintf("https://avwx.rest/api/metar/%s?options=&airport=true&reporting=true&format=json&onfail=cache", loc)
|
|
|
|
msg = fmt.Sprintf("calculated url: `%s`", url)
|
|
b.SendMsgToChannel(msg, postid, channelid)
|
|
|
|
client := http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
req.Header.Add("Authorization", `Token `+token)
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
b.SendMsgToChannel(fmt.Sprintf("weather fetch error: %s", err), postid, channelid)
|
|
return
|
|
}
|
|
|
|
b.SendMsgToChannel(fmt.Sprintf("weather %s: %s", loc, resp), postid, channelid)
|
|
|
|
}
|
|
|
|
func (b *Bot) HandleMsgFromChannel(event *model.WebSocketEvent) {
|
|
|
|
post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
|
|
if post == nil {
|
|
return
|
|
}
|
|
|
|
//pretty.Print(post)
|
|
if matched, _ := regexp.MatchString(`(?:^|\W)metar(?:$|\W)`, post.Message); matched {
|
|
b.HandleWeatherRequest(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)
|
|
return
|
|
}
|
|
|
|
if matched, _ := regexp.MatchString(`(?:^|\W)version(?:$|\W)`, post.Message); matched {
|
|
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)
|
|
return
|
|
}
|
|
|
|
if matched, _ := regexp.MatchString(`(?:^|\W)uptime(?:$|\W)`, post.Message); matched {
|
|
msg := fmt.Sprintf("running: uptime %d seconds", b.Uptime())
|
|
b.SendMsgToChannel(msg, post.Id, post.ChannelId)
|
|
return
|
|
}
|
|
|
|
b.SendMsgToChannel("I did not understand your command, sorry", post.Id, post.ChannelId)
|
|
|
|
}
|
|
|
|
func (b *Bot) Uptime() int64 {
|
|
return (time.Now().Unix() - b.StartupUnixTime)
|
|
}
|
|
|
|
func (b *Bot) HandleMsgFromDebuggingChannel(event *model.WebSocketEvent) {
|
|
println("responding to debugging channel msg")
|
|
|
|
post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
|
|
if post != nil {
|
|
|
|
// ignore my events
|
|
if matched, _ := regexp.MatchString(`(?:^|\W)shutdown(?:$|\W)`, post.Message); matched {
|
|
b.Shutdown()
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
b.SendMsgToChannel("I did not understand your command, sorry", post.Id, post.ChannelId)
|
|
}
|
|
|
|
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 {
|
|
msg := fmt.Sprintf("_**%s** (version `%s`) is now shutting down_\n\nuptime was %d secs", b.BotName, b.Version, b.Uptime())
|
|
b.SendMsgToDebuggingChannel(msg, "")
|
|
if b.webSocketClient != nil {
|
|
b.webSocketClient.Close()
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
}()
|
|
}
|