From 3244f284fd5be736bb830d7774687acc790af88a Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Thu, 11 Sep 2025 06:44:50 -0500 Subject: [PATCH] feat(discord bot): added in a ping host command to get the bot going :D more commands will be added as we continue working on this bot, including service restarts, log monitors --- controller/go.mod | 13 +++-- controller/go.sum | 30 ++++++++++ controller/internal/bot/bot.go | 81 +++++++++++++++++++++++++- controller/internal/bot/commands.go | 64 ++++++++++++++++++++ controller/internal/bot/interaction.go | 18 ++++++ controller/internal/bot/ping_device.go | 59 +++++++++++++++++++ controller/main.go | 2 + 7 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 controller/internal/bot/commands.go create mode 100644 controller/internal/bot/interaction.go create mode 100644 controller/internal/bot/ping_device.go diff --git a/controller/go.mod b/controller/go.mod index 6266302..cf47b30 100644 --- a/controller/go.mod +++ b/controller/go.mod @@ -9,6 +9,7 @@ require ( ) require ( + github.com/bwmarrin/discordgo v0.29.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -16,12 +17,14 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-ping/ping v1.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/gomodule/redigo v1.8.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hirochachacha/go-smb2 v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -31,15 +34,17 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus-community/pro-bing v0.7.0 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/controller/go.sum b/controller/go.sum index ea0ffb7..b16370d 100644 --- a/controller/go.sum +++ b/controller/go.sum @@ -1,3 +1,5 @@ +github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= +github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -16,6 +18,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ= +github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -29,6 +33,10 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gomodule/redigo v1.8.4 h1:Z5JUg94HMTR1XpwBaSH4vq3+PNSIykBLxMdglbw10gg= github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googollee/go-socket.io v1.7.0 h1:ODcQSAvVIPvKozXtUGuJDV3pLwdpBLDs1Uoq/QHIlY8= github.com/googollee/go-socket.io v1.7.0/go.mod h1:0vGP8/dXR9SZUMMD4+xxaGo/lohOw3YWMh2WRiWeKxg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -55,6 +63,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-community/pro-bing v0.7.0 h1:KFYFbxC2f2Fp6c+TyxbCOEarf7rbnzr9Gw8eIb0RfZA= +github.com/prometheus-community/pro-bing v0.7.0/go.mod h1:Moob9dvlY50Bfq6i88xIwfyw7xLFHH69LUgx9n5zqCE= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= @@ -80,20 +90,40 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/controller/internal/bot/bot.go b/controller/internal/bot/bot.go index 1365f35..374c282 100644 --- a/controller/internal/bot/bot.go +++ b/controller/internal/bot/bot.go @@ -1,5 +1,84 @@ package bot -func main() { +import ( + "context" + "fmt" + "os" + "os/signal" + "github.com/bwmarrin/discordgo" +) + +func TheBot() { + + Token := os.Getenv("BOT_KEY") + if Token == "" { + fmt.Println("You must set DISCORD_TOKEN environment variable.") + return + } + + // Create bot session + dg, err := discordgo.New("Bot " + Token) + if err != nil { + fmt.Println("Error creating Discord session,", err) + return + } + + dg.Identify.Intents = discordgo.IntentsGuildMessages | + discordgo.IntentsDirectMessages | + discordgo.IntentsMessageContent | discordgo.IntentsGuilds + + // Register messageCreate as a callback for MessageCreate events + dg.AddHandler(interactionCreate) + + // // Open the websocket and begin listening + err = dg.Open() + if err != nil { + fmt.Println("Error opening Discord session,", err) + return + } + + // Wait here until CTRL-C or other term signal is received. + fmt.Println("Bot is now running. Press CTRL-C to exit.") + // Register commands, passing the session + registerCommands(dg) + + // sc := make(chan os.Signal, 1) + // signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + // <-sc + + // // Cleanly close down the Discord session. + // dg.Close() + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + <-ctx.Done() + + cleanupCommands(dg) + dg.Close() } + +// This function will be called (due to event handler registration above) +// every time a new message is created on any channel that the authed bot has access to. +// func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { +// // ignore bots (including self) +// if m.Author == nil || m.Author.Bot { +// return +// } + +// // debug: log incoming messages so you can see them in console +// fmt.Printf("msg from %s: %s\n", m.Author.Username, m.Content) + +// // choose the prefix you want — here I check for "!ping" +// if m.Content == "!ping" { +// if _, err := s.ChannelMessageSend(m.ChannelID, "Pong!"); err != nil { +// fmt.Println("send error:", err) +// } +// } +// if m.Content == "!pong" { +// if _, err := s.ChannelMessageSend(m.ChannelID, "Ping!"); err != nil { +// fmt.Println("send error:", err) +// } +// } +// } diff --git a/controller/internal/bot/commands.go b/controller/internal/bot/commands.go new file mode 100644 index 0000000..2e601ac --- /dev/null +++ b/controller/internal/bot/commands.go @@ -0,0 +1,64 @@ +package bot + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" +) + +var registeredCommands = []*discordgo.ApplicationCommand{ + { + Name: "ping", + Description: "Ping a server to make sure it's still online.", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "host", + Description: "The server hostname or IP to ping", + Required: true, + }, + }, + }, + // future commands can be added here +} + +func registerCommands(s *discordgo.Session) { + existing, err := s.ApplicationCommands(s.State.User.ID, "") + if err != nil { + fmt.Println("Failed to list existing commands:", err) + return + } + + for _, cmd := range registeredCommands { + alreadyExists := false + for _, e := range existing { + if e.Name == cmd.Name { + alreadyExists = true + break + } + } + + if alreadyExists { + fmt.Printf("Command '%s' already exists, skipping registration.\n", cmd.Name) + continue + } + + _, err := s.ApplicationCommandCreate(s.State.User.ID, "", cmd) + if err != nil { + fmt.Printf("Cannot create command '%s': %v\n", cmd.Name, err) + } else { + fmt.Printf("Registered command: %s\n", cmd.Name) + } + } +} + +// Cleanup commands on shutdown +func cleanupCommands(s *discordgo.Session) { + commands, err := s.ApplicationCommands(s.State.User.ID, "") + if err != nil { + return + } + for _, cmd := range commands { + _ = s.ApplicationCommandDelete(s.State.User.ID, "", cmd.ID) + } +} diff --git a/controller/internal/bot/interaction.go b/controller/internal/bot/interaction.go new file mode 100644 index 0000000..a395bf1 --- /dev/null +++ b/controller/internal/bot/interaction.go @@ -0,0 +1,18 @@ +package bot + +import ( + "github.com/bwmarrin/discordgo" +) + +func interactionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { + if i.Type != discordgo.InteractionApplicationCommand { + return + } + + switch i.ApplicationCommandData().Name { + case "ping": + // Get the host parameter + // sending s sends the session over to pinger, then i is the interactions we send over + HandlePingCommand(s, i) + } +} diff --git a/controller/internal/bot/ping_device.go b/controller/internal/bot/ping_device.go new file mode 100644 index 0000000..9e48a66 --- /dev/null +++ b/controller/internal/bot/ping_device.go @@ -0,0 +1,59 @@ +package bot + +import ( + "fmt" + "net" + + "github.com/bwmarrin/discordgo" +) + +func HandlePingCommand(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Get the host parameter + options := i.ApplicationCommandData().Options + host := options[0].StringValue() + + // Optionally, do a DNS lookup or ICMP ping + ips, err := net.LookupIP(host) + if err != nil { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Could not resolve host `%s`: %v", host, err), + }, + }) + return + } + + // // host is the user-provided hostname + // pinger, err := probing.NewPinger(host) + // if err != nil { + // s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + // Type: discordgo.InteractionResponseChannelMessageWithSource, + // Data: &discordgo.InteractionResponseData{ + // Content: fmt.Sprintf("Error creating pinger: %v", err), + // }, + // }) + // return + // } + // pinger.Count = 3 + // pinger.SetPrivileged(false) // <- key for Windows non-admin + // err = pinger.Run() + // if err != nil { + // s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + // Type: discordgo.InteractionResponseChannelMessageWithSource, + // Data: &discordgo.InteractionResponseData{ + // Content: fmt.Sprintf("Error running ping: %v", err), + // }, + // }) + // return + // } + // stats := pinger.Statistics() + + // Respond with the first IP found + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Host `%s` resolves to %s ✅", host, ips[0].String()), + }, + }) +} diff --git a/controller/main.go b/controller/main.go index 3960067..3ce532d 100644 --- a/controller/main.go +++ b/controller/main.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" socketio "github.com/googollee/go-socket.io" "github.com/joho/godotenv" + "lst.net/internal/bot" "lst.net/pkg" ) @@ -138,6 +139,7 @@ func main() { } }() + go bot.TheBot() go server.Serve() defer server.Close()