- replace commands with slash commands

- create event modal
This commit is contained in:
Kristian 2024-11-13 16:29:54 +01:00
parent 228cf2e621
commit cf60b9bcec
4 changed files with 357 additions and 242 deletions

View File

@ -6,7 +6,6 @@ import (
"math/rand"
"os"
"os/signal"
"strconv"
"strings"
"github.com/bwmarrin/discordgo"
@ -18,7 +17,7 @@ var (
OpenAIToken string
CryptoToken string
AllowedUserID string
AppId string
AppId string
)
func Run() {
@ -27,22 +26,24 @@ func Run() {
log.Fatal(err)
}
discord.AddHandler(newMessage)
discord.AddHandler(commandHandler)
discord.AddHandler(newMessage)
discord.AddHandler(modalHandler)
log.Info("Opening Discord connection...")
err = discord.Open()
if err != nil {
log.Fatal(err)
}
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("PocketBase initialized successfully.")
log.Info("Exiting... press CTRL + c again")
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
@ -74,244 +75,359 @@ func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) {
channelID := message.ChannelID
conversationHistory = populateConversationHistory(discord, channelID, conversationHistory)
if strings.HasPrefix(message.Content, "!cry") {
currentCryptoPrice := getCurrentCryptoPrice(message.Content)
discord.ChannelMessageSendComplex(message.ChannelID, currentCryptoPrice)
} else if strings.HasPrefix(message.Content, "!bit") || isPrivateChannel {
if strings.HasPrefix(message.Content, "!bit") || isPrivateChannel {
chatGPT(discord, message.ChannelID, conversationHistory)
} else if strings.HasPrefix(message.Content, "!genkey") {
if hasAdminRole(message.Member.Roles) {
err := GenerateAndSaveSSHKeyPairIfNotExist()
if err != nil {
discord.ChannelMessageSend(message.ChannelID, "Error generating or saving key pair.")
return
}
discord.ChannelMessageSend(message.ChannelID, "SSH key pair generated and saved successfully!")
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!showkey") {
if hasAdminRole(message.Member.Roles) {
publicKey, err := GetPublicKey()
if err != nil {
discord.ChannelMessageSend(message.ChannelID, "Error fetching public key.")
return
}
discord.ChannelMessageSend(message.ChannelID, publicKey)
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!regenkey") {
if hasAdminRole(message.Member.Roles) {
err := GenerateAndSaveSSHKeyPair()
if err != nil {
discord.ChannelMessageSend(message.ChannelID, "Error regenerating and saving key pair.")
return
}
discord.ChannelMessageSend(message.ChannelID, "SSH key pair regenerated and saved successfully!")
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!ssh") {
if hasAdminRole(message.Member.Roles) {
commandParts := strings.Fields(message.Content)
if len(commandParts) != 2 {
discord.ChannelMessageSend(message.ChannelID, "Invalid command format. Use !ssh username@remote-host:port")
return
}
connectionDetails := commandParts[1]
sshConn, err := SSHConnectToRemoteServer(connectionDetails)
if err != nil {
discord.ChannelMessageSend(message.ChannelID, "Error connecting to remote server.")
return
}
// Store the SSH connection for later use
sshConnections[message.Author.ID] = sshConn
// Save server information to PocketBase
serverInfo := &pb.ServerInfo{
UserID: message.Author.ID,
ConnectionDetails: connectionDetails,
}
err = pb.CreateRecord("servers", serverInfo)
if err != nil {
log.Error(err)
discord.ChannelMessageSend(message.ChannelID, "Error saving server information.")
return
}
discord.ChannelMessageSend(message.ChannelID, "Connected to remote server!")
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!exe") {
if hasAdminRole(message.Member.Roles) {
// Check if there is an active SSH connection for this user
sshConn, ok := sshConnections[message.Author.ID]
if !ok {
discord.ChannelMessageSend(message.ChannelID, "You are not connected to any remote server. Use !ssh first.")
return
}
// Extract the command after "!exe"
command := strings.TrimPrefix(message.Content, "!exe ")
// Execute the command on the remote server
response, err := sshConn.ExecuteCommand(command)
if err != nil {
discord.ChannelMessageSend(message.ChannelID, "Error executing command on remote server.")
return
}
discord.ChannelMessageSend(message.ChannelID, ">\n "+response)
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!exit") {
if hasAdminRole(message.Member.Roles) {
// Check if there is an active SSH connection for this user
sshConn, ok := sshConnections[message.Author.ID]
if !ok {
discord.ChannelMessageSend(message.ChannelID, "You are not connected to any remote server. Use !ssh first.")
return
}
// Close the SSH connection
sshConn.Close()
// Remove the SSH connection from the map
delete(sshConnections, message.Author.ID)
discord.ChannelMessageSend(message.ChannelID, "SSH connection closed.")
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!list") {
if hasAdminRole(message.Member.Roles) {
// Retrieve the list of servers for the user
servers, err := pb.ListServersByUserID(message.Author.ID)
if err != nil {
log.Error("Error listing servers:", err)
discord.ChannelMessageSend(message.ChannelID, "Error listing servers.")
return
}
// Check if there are any servers
if len(servers) == 0 {
discord.ChannelMessageSend(message.ChannelID, "You don't have any servers.")
return
}
// Build a message with the list of servers
var serverListMessage strings.Builder
serverListMessage.WriteString("Recent servers:\n")
for _, server := range servers {
serverListMessage.WriteString(fmt.Sprintf(server.ConnectionDetails))
// Add other fields as needed
}
// Send the server list message to Discord
discord.ChannelMessageSend(message.ChannelID, serverListMessage.String())
} else {
discord.ChannelMessageSend(message.ChannelID, "You are not authorized to use this command.")
}
} else if strings.HasPrefix(message.Content, "!help") {
if strings.Contains(message.Content, "admin") {
adminHelpMessage := "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 username@remote-host:port - Connect to a remote server via SSH.\n" +
"!list saved servers. (auto save on first connect)\n" +
"!exe command - Execute a command on the remote server (after !ssh).\n" +
"!exit - Close the SSH connection (after !ssh).\n"
discord.ChannelMessageSend(message.ChannelID, adminHelpMessage)
} else {
generalHelpMessage := "Available commands:\n" +
"!cry - Get information about cryptocurrency prices.\n" +
"!bit - Interact with the BitBot chatbot.\n" +
"!roll - Random number from 1-100 or specify a number (!roll 9001).\n" +
"!help - Show available commands.\n" +
"!help admin - Show admin commands.\n"
discord.ChannelMessageSend(message.ChannelID, generalHelpMessage)
}
} else if strings.HasPrefix(message.Content, "!roll") {
handleRollCommand(discord, message)
}
conversationHistoryMap[userID] = conversationHistory
}
func handleRollCommand(session *discordgo.Session, message *discordgo.MessageCreate) {
// Extract the argument from the message
args := strings.Fields(message.Content[len("!roll"):])
if len(args) == 0 {
// No argument provided, generate random number between 1 and 100
randomNumber := rand.Intn(100) + 1
session.ChannelMessageSend(message.ChannelID, fmt.Sprintf("You rolled a %d!", randomNumber))
} else {
// Argument provided, try to parse it as a number
if num, err := strconv.Atoi(args[0]); err == nil {
// Generate random number between 1 and the provided number
randomNumber := rand.Intn(num) + 1
session.ChannelMessageSend(message.ChannelID, fmt.Sprintf("%d", randomNumber))
} else {
session.ChannelMessageSend(message.ChannelID, "Invalid argument. Please provide a valid number.")
}
}
}
func registerCommands(discord *discordgo.Session, appID string) {
cmd := &discordgo.ApplicationCommand{
Name: "ping",
Description: "Check the bot's ping or specify something to ping.",
Options: []*discordgo.ApplicationCommandOption{
{
Name: "something",
Description: "Specify something to ping.",
Type: discordgo.ApplicationCommandOptionString, // Use the appropriate type
Required: false, // Set to true if you want it to be mandatory
},
},
}
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,
},
},
},
}
_, err := discord.ApplicationCommandCreate(appID, "", cmd)
if err != nil {
log.Fatalf("Cannot create slash command: %v", err)
}
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 {
data := i.ApplicationCommandData()
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)
if data.Name == "ping" {
// Default response
response := "Pong!"
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.")
}
// Check if the user provided the "something" option
for _, option := range data.Options {
if option.Name == "something" {
// Update response if "something" was provided
response = "Pong! You specified: " + option.StringValue()
}
}
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.")
}
// Send the response
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: response,
Flags: discordgo.MessageFlagsEphemeral, // Optional: make the response ephemeral
},
})
if err != nil {
log.Printf("Error responding to interaction: %v", err)
}
}
}
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)
}
}
}

View File

@ -15,7 +15,7 @@ const (
maxTokens = 1500
maxContextTokens = 2000
maxMessageTokens = 2000
systemMessageText = "your name is !bit you are a discord bot, you use brief answers untill asked to elaborate or explain. You also have these commands that users can use: Available commands:!cry - Get information about cryptocurrency prices.!bit - Interact with the BitBot chatbot.!roll - Random number from 1-100 or specify a number (!roll 9001).!help - Show available commands."
systemMessageText = "your name is !bit you are a discord bot, you use brief answers untill asked to elaborate or explain."
)
var lastChannelID string // Track the last used channelID globally

View File

@ -37,7 +37,6 @@ func Init() {
func GetRecordById(collectionID string, recordID string) (*models.Record, error) {
// Access the PocketBase DAO and perform database operations
// Example: Retrieve all records from the "articles" collection
record, err := app.Dao().FindRecordById(collectionID, recordID)
if err != nil {
log.Error(err)
@ -113,17 +112,17 @@ func ListServersByUserID(userID string) ([]*ServerInfo, error) {
}
// Log the number of records retrieved
log.Info("Number of records:", len(records))
log.Info("Number of records: %d", len(records))
// Convert records to []*ServerInfo
var servers []*ServerInfo
for _, record := range records {
// Log details of each record
log.Info("Record details:", record)
log.Info("Record details: %v", record)
// Print each field to check if they are set
log.Info("UserID:", record.Get("UserID"))
log.Info("ConnectionDetails:", record.Get("ConnectionDetails"))
log.Info("UserID: %d", record.Get("UserID"))
log.Info("ConnectionDetails: %v", record.Get("ConnectionDetails"))
// Convert record to ServerInfo
server := &ServerInfo{

Binary file not shown.