package rcon import ( "context" "fmt" "os" "time" "github.com/gorcon/rcon" ) // Client represents an RCON client for Minecraft server interaction type Client struct { conn *rcon.Conn } // New creates a new RCON client func New(address, password string) (*Client, error) { conn, err := rcon.Dial(address, password) if err != nil { return nil, fmt.Errorf("failed to connect to RCON: %w", err) } return &Client{ conn: conn, }, nil } // Execute executes a command on the Minecraft server func (c *Client) Execute(command string) (string, error) { if c.conn == nil { return "", fmt.Errorf("RCON connection not established") } response, err := c.conn.Execute(command) if err != nil { return "", fmt.Errorf("failed to execute command '%s': %w", command, err) } return response, nil } // Close closes the RCON connection func (c *Client) Close() error { if c.conn != nil { return c.conn.Close() } return nil } // Ping checks if the RCON connection is alive func (c *Client) Ping() error { _, err := c.Execute("help") return err } // SetWeather sets the weather in the Minecraft world func (c *Client) SetWeather(weather string) error { var command string switch weather { case "clear": command = "weather clear" case "rain": command = "weather rain" case "thunder": command = "weather thunder" default: return fmt.Errorf("invalid weather type: %s", weather) } _, err := c.Execute(command) return err } // SetTime sets the time in the Minecraft world func (c *Client) SetTime(timeValue string) error { var command string switch timeValue { case "day": command = "time set day" case "night": command = "time set night" case "noon": command = "time set noon" case "midnight": command = "time set midnight" default: // Assume it's a numeric value command = fmt.Sprintf("time set %s", timeValue) } _, err := c.Execute(command) return err } // SetDifficulty sets the difficulty level func (c *Client) SetDifficulty(difficulty string) error { var command string switch difficulty { case "peaceful": command = "difficulty peaceful" case "easy": command = "difficulty easy" case "normal": command = "difficulty normal" case "hard": command = "difficulty hard" default: return fmt.Errorf("invalid difficulty level: %s", difficulty) } _, err := c.Execute(command) return err } // GetServerInfo returns basic server information func (c *Client) GetServerInfo() (string, error) { return c.Execute("version") } // TailLogs starts tailing the server logs func (c *Client) TailLogs(ctx context.Context, handler func(string)) error { // This is a placeholder implementation // In a real implementation, this would need to be connected to actual log tailing // For now, we'll just return an error to indicate this is not implemented return fmt.Errorf("log tailing not implemented in this version") } // ConnectWithTimeout attempts to connect with a timeout func ConnectWithTimeout(address, password string, timeout time.Duration) (*Client, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // Create a channel to receive the connection result connChan := make(chan *Client, 1) errChan := make(chan error, 1) go func() { client, err := New(address, password) if err != nil { errChan <- err return } connChan <- client }() select { case client := <-connChan: return client, nil case err := <-errChan: return nil, err case <-ctx.Done(): return nil, fmt.Errorf("timeout connecting to RCON: %w", ctx.Err()) } } // GetEnvCredentials returns RCON address and password from environment variables func GetEnvCredentials() (string, string, error) { address := os.Getenv("RCON_ADDRESS") if address == "" { return "", "", fmt.Errorf("RCON_ADDRESS environment variable not set") } password := os.Getenv("RCON_PASSWORD") if password == "" { return "", "", fmt.Errorf("RCON_PASSWORD environment variable not set") } return address, password, nil } // NewFromEnv creates a new RCON client using environment variables func NewFromEnv() (*Client, error) { address, password, err := GetEnvCredentials() if err != nil { return nil, err } client, err := New(address, password) if err != nil { return nil, fmt.Errorf("failed to create RCON client from environment: %w", err) } return client, nil } // HealthCheck verifies the RCON connection is working properly func (c *Client) HealthCheck() error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() select { case <-ctx.Done(): return fmt.Errorf("health check timeout") default: if err := c.Ping(); err != nil { return fmt.Errorf("health check failed: %w", err) } return nil } } // ExecuteWithTimeout executes a command with a timeout func (c *Client) ExecuteWithTimeout(command string, timeout time.Duration) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // Create a channel to receive the response responseChan := make(chan string, 1) errChan := make(chan error, 1) go func() { response, err := c.Execute(command) if err != nil { errChan <- err return } responseChan <- response }() select { case response := <-responseChan: return response, nil case err := <-errChan: return "", err case <-ctx.Done(): return "", fmt.Errorf("timeout executing command '%s': %w", command, ctx.Err()) } }