434 lines
13 KiB
Go
434 lines
13 KiB
Go
package bot
|
|
|
|
import (
|
|
"bitbot/pb"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
"github.com/charmbracelet/log"
|
|
)
|
|
|
|
var (
|
|
BotToken string
|
|
OpenAIToken string
|
|
CryptoToken string
|
|
AllowedUserID string
|
|
AppId string
|
|
)
|
|
|
|
func Run() {
|
|
discord, err := discordgo.New("Bot " + BotToken)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
discord.AddHandler(commandHandler)
|
|
discord.AddHandler(newMessage)
|
|
discord.AddHandler(modalHandler)
|
|
|
|
log.Info("Opening Discord connection...")
|
|
err = discord.Open()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer discord.Close()
|
|
log.Info("Registering commands...")
|
|
registerCommands(discord, AppId)
|
|
log.Info("BitBot is running...")
|
|
|
|
// Try initializing PocketBase after Discord is connected
|
|
log.Info("Initializing PocketBase...")
|
|
pb.Init()
|
|
log.Info("Exiting... press CTRL + c again")
|
|
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt)
|
|
<-c
|
|
}
|
|
|
|
var conversationHistoryMap = make(map[string][]map[string]interface{})
|
|
var sshConnections = make(map[string]*SSHConnection)
|
|
|
|
func hasAdminRole(roles []string) bool {
|
|
for _, role := range roles {
|
|
if role == AllowedUserID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) {
|
|
if message.Author.ID == discord.State.User.ID || message.Content == "" {
|
|
return
|
|
}
|
|
|
|
isPrivateChannel := message.GuildID == ""
|
|
|
|
userID := message.Author.ID
|
|
conversationHistory := conversationHistoryMap[userID]
|
|
|
|
channelID := message.ChannelID
|
|
conversationHistory = populateConversationHistory(discord, channelID, conversationHistory)
|
|
|
|
if strings.HasPrefix(message.Content, "!bit") || isPrivateChannel {
|
|
chatGPT(discord, message.ChannelID, conversationHistory)
|
|
}
|
|
}
|
|
|
|
func registerCommands(discord *discordgo.Session, appID string) {
|
|
commands := []*discordgo.ApplicationCommand{
|
|
{Name: "cry", Description: "Get information about cryptocurrency prices."},
|
|
{Name: "genkey", Description: "Generate and save SSH key pair."},
|
|
{Name: "showkey", Description: "Show the public SSH key."},
|
|
{Name: "regenkey", Description: "Regenerate and save SSH key pair."},
|
|
{Name: "createevent", Description: "Organize an Ava dungeon raid event."},
|
|
{
|
|
Name: "ssh",
|
|
Description: "Connect to a remote server via SSH.",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "connection_details",
|
|
Description: "Connection details in the format username@remote-host:port",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "exe",
|
|
Description: "Execute a command on the remote server.",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "command",
|
|
Description: "The command to execute.",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
{Name: "exit", Description: "Close the SSH connection."},
|
|
{Name: "list", Description: "List saved servers."},
|
|
{
|
|
Name: "help",
|
|
Description: "Show available commands.",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "category",
|
|
Description: "Specify 'admin' to view admin commands.",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "roll",
|
|
Description: "Roll a random number.",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "max",
|
|
Description: "Specify the maximum number for the roll.",
|
|
Type: discordgo.ApplicationCommandOptionInteger,
|
|
Required: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, cmd := range commands {
|
|
_, err := discord.ApplicationCommandCreate(appID, "", cmd)
|
|
if err != nil {
|
|
log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func commandHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
if i.Type == discordgo.InteractionApplicationCommand {
|
|
// Only process application command interactions
|
|
data := i.ApplicationCommandData()
|
|
switch data.Name {
|
|
case "createevent":
|
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseModal,
|
|
Data: &discordgo.InteractionResponseData{
|
|
CustomID: "event_modal",
|
|
Title: "Create an Ava Dungeon Raid",
|
|
Components: []discordgo.MessageComponent{
|
|
discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.TextInput{
|
|
CustomID: "event_title",
|
|
Label: "Event Title",
|
|
Style: discordgo.TextInputShort,
|
|
Placeholder: "Enter the raid title",
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.TextInput{
|
|
CustomID: "event_date",
|
|
Label: "Event Date",
|
|
Style: discordgo.TextInputShort,
|
|
Placeholder: "e.g., 15-11-2024",
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.TextInput{
|
|
CustomID: "event_time",
|
|
Label: "Event Time",
|
|
Style: discordgo.TextInputShort,
|
|
Placeholder: "e.g., 18:00 UTC",
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.TextInput{
|
|
CustomID: "event_note",
|
|
Label: "Additional Notes (optional)",
|
|
Style: discordgo.TextInputParagraph,
|
|
Placeholder: "Any extra details or instructions",
|
|
Required: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Printf("Error responding with modal: %v", err)
|
|
}
|
|
case "cry":
|
|
currentCryptoPrice := getCurrentCryptoPrice(data.Options[0].StringValue())
|
|
respondWithMessage(s, i, currentCryptoPrice)
|
|
|
|
case "genkey":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
err := GenerateAndSaveSSHKeyPairIfNotExist()
|
|
response := "SSH key pair generated and saved successfully!"
|
|
if err != nil {
|
|
response = "Error generating or saving key pair."
|
|
}
|
|
respondWithMessage(s, i, response)
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "showkey":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
publicKey, err := GetPublicKey()
|
|
response := publicKey
|
|
if err != nil {
|
|
response = "Error fetching public key."
|
|
}
|
|
respondWithMessage(s, i, response)
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "regenkey":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
err := GenerateAndSaveSSHKeyPair()
|
|
response := "SSH key pair regenerated and saved successfully!"
|
|
if err != nil {
|
|
response = "Error regenerating and saving key pair."
|
|
}
|
|
respondWithMessage(s, i, response)
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "ssh":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
connectionDetails := data.Options[0].StringValue()
|
|
sshConn, err := SSHConnectToRemoteServer(connectionDetails)
|
|
response := "Connected to remote server!"
|
|
if err != nil {
|
|
response = "Error connecting to remote server."
|
|
} else {
|
|
sshConnections[i.Member.User.ID] = sshConn
|
|
serverInfo := &pb.ServerInfo{UserID: i.Member.User.ID, ConnectionDetails: connectionDetails}
|
|
err = pb.CreateRecord("servers", serverInfo)
|
|
if err != nil {
|
|
log.Error(err)
|
|
response = "Error saving server information."
|
|
}
|
|
}
|
|
respondWithMessage(s, i, response)
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "exe":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
sshConn, ok := sshConnections[i.Member.User.ID]
|
|
if !ok {
|
|
respondWithMessage(s, i, "You are not connected to any remote server. Use /ssh first.")
|
|
return
|
|
}
|
|
command := data.Options[0].StringValue()
|
|
response, err := sshConn.ExecuteCommand(command)
|
|
if err != nil {
|
|
response = "Error executing command on remote server."
|
|
}
|
|
respondWithMessage(s, i, response)
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "exit":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
sshConn, ok := sshConnections[i.Member.User.ID]
|
|
if !ok {
|
|
respondWithMessage(s, i, "You are not connected to any remote server. Use /ssh first.")
|
|
return
|
|
}
|
|
sshConn.Close()
|
|
delete(sshConnections, i.Member.User.ID)
|
|
respondWithMessage(s, i, "SSH connection closed.")
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "list":
|
|
if hasAdminRole(i.Member.Roles) {
|
|
servers, err := pb.ListServersByUserID(i.Member.User.ID)
|
|
if err != nil || len(servers) == 0 {
|
|
respondWithMessage(s, i, "You don't have any servers.")
|
|
return
|
|
}
|
|
var serverListMessage strings.Builder
|
|
serverListMessage.WriteString("Recent servers:\n")
|
|
for _, server := range servers {
|
|
serverListMessage.WriteString(fmt.Sprintf("%s\n", server.ConnectionDetails))
|
|
}
|
|
respondWithMessage(s, i, serverListMessage.String())
|
|
} else {
|
|
respondWithMessage(s, i, "You are not authorized to use this command.")
|
|
}
|
|
|
|
case "help":
|
|
helpMessage := "Available commands:\n" +
|
|
"/cry - Get information about cryptocurrency prices.\n" +
|
|
"/roll - Roll a random number.\n" +
|
|
"/help - Show available commands.\n"
|
|
if len(data.Options) > 0 && data.Options[0].StringValue() == "admin" {
|
|
helpMessage += "Admin commands:\n" +
|
|
"/genkey - Generate and save SSH key pair.\n" +
|
|
"/showkey - Show the public key.\n" +
|
|
"/regenkey - Regenerate and save SSH key pair.\n" +
|
|
"/ssh - Connect to a remote server via SSH.\n" +
|
|
"/exe - Execute a command on the remote server.\n" +
|
|
"/exit - Close the SSH connection.\n" +
|
|
"/list - List saved servers.\n"
|
|
}
|
|
respondWithMessage(s, i, helpMessage)
|
|
|
|
case "roll":
|
|
max := 100
|
|
if len(data.Options) > 0 {
|
|
max = int(data.Options[0].IntValue())
|
|
}
|
|
result := rand.Intn(max) + 1
|
|
respondWithMessage(s, i, fmt.Sprintf("You rolled: %d", result))
|
|
}
|
|
} else if i.Type == discordgo.InteractionModalSubmit {
|
|
// Pass modal submissions to the modalHandler function
|
|
modalHandler(s, i)
|
|
}
|
|
}
|
|
|
|
func respondWithMessage(s *discordgo.Session, i *discordgo.InteractionCreate, message interface{}) {
|
|
var response *discordgo.InteractionResponseData
|
|
|
|
switch v := message.(type) {
|
|
case string:
|
|
response = &discordgo.InteractionResponseData{
|
|
Content: v,
|
|
Flags: discordgo.MessageFlagsEphemeral, // To make it private to the user
|
|
}
|
|
case *discordgo.MessageSend:
|
|
response = &discordgo.InteractionResponseData{
|
|
Content: v.Content,
|
|
Embeds: v.Embeds,
|
|
Flags: discordgo.MessageFlagsEphemeral, // To make it private to the user
|
|
}
|
|
default:
|
|
response = &discordgo.InteractionResponseData{
|
|
Content: "Unknown response type.",
|
|
Flags: discordgo.MessageFlagsEphemeral, // To make it private to the user
|
|
}
|
|
}
|
|
|
|
// Send the response back to the interaction
|
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: response,
|
|
})
|
|
}
|
|
|
|
func modalHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
if i.Type == discordgo.InteractionModalSubmit && i.ModalSubmitData().CustomID == "event_modal" {
|
|
data := i.ModalSubmitData()
|
|
|
|
// Retrieve values from the modal submission
|
|
title := data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value
|
|
date := data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value
|
|
time := data.Components[2].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value
|
|
note := data.Components[3].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value
|
|
|
|
// Create the event announcement message
|
|
response := " \n"
|
|
response += " **Ava Dungeon Raid Event Created!** \n"
|
|
response += "**Title**: " + title + "\n"
|
|
response += "**Date**: " + date + "\n"
|
|
response += "**Time**: " + time + "\n"
|
|
if note != "" {
|
|
response += "**Note**: " + note
|
|
}
|
|
|
|
// Respond with the event announcement and RSVP buttons
|
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: response,
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
&discordgo.Button{
|
|
Label: "Coming",
|
|
CustomID: "rsvp_coming",
|
|
Style: discordgo.PrimaryButton,
|
|
},
|
|
&discordgo.Button{
|
|
Label: "Benched",
|
|
CustomID: "rsvp_bench",
|
|
Style: discordgo.SecondaryButton,
|
|
},
|
|
&discordgo.Button{
|
|
Label: "Not Coming",
|
|
CustomID: "rsvp_not_coming",
|
|
Style: discordgo.DangerButton,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Printf("Error responding to modal submission: %v", err)
|
|
}
|
|
}
|
|
}
|