xapi-cli/cmd/status.go
Soldier 94635e7ace feat: MVP release - OAuth 1.0a CLI for X API
Complete CLI tool with 4 core commands:
- xapi login: Configure OAuth credentials via editor
- xapi status: Test authentication
- xapi search: Search tweets with preview/execute modes
- xapi create: Post tweets with preview/execute modes

Features:
- OAuth 1.0a authentication with HMAC-SHA1 signing
- OAuth 2.0 Client ID/Secret support (for future features)
- TOML-based configuration
- Editor integration for config management
- Helpful error messages for permission issues
- Quota-aware design (no caching to avoid complexity)

Built for developers on Free/Basic X API tiers.
2025-11-13 21:46:18 +00:00

176 lines
4.3 KiB
Go

/*
Copyright © 2025 maxtheweb
*/
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
"github.com/BurntSushi/toml"
"github.com/spf13/cobra"
)
// Config structure
type Config struct {
OAuth struct {
ConsumerKey string `toml:"consumer_key"`
ConsumerSecret string `toml:"consumer_secret"`
AccessToken string `toml:"access_token"`
AccessTokenSecret string `toml:"access_token_secret"`
} `toml:"oauth"`
OAuth2 struct {
ClientID string `toml:"client_id"`
ClientSecret string `toml:"client_secret"`
} `toml:"oauth2"`
}
// UserMeResponse from X API /2/users/me
type UserMeResponse struct {
Data struct {
ID string `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
} `json:"data"`
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
// statusCmd represents the status command
var statusCmd = &cobra.Command{
Use: "status",
Short: "Test OAuth authentication",
Long: `Test your OAuth 1.0a credentials by authenticating with X API.
This command verifies that your credentials are valid by making an
authenticated request to /2/users/me to fetch your user information.
Run this after 'xapi login' to confirm your setup is correct.`,
Run: func(cmd *cobra.Command, args []string) {
runStatus()
},
}
func init() {
rootCmd.AddCommand(statusCmd)
}
func runStatus() {
fmt.Println("\nTESTING AUTHENTICATION")
fmt.Println("======================")
// Load OAuth config
config, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
fmt.Println("\nRun 'xapi login' to configure your credentials")
os.Exit(1)
}
// Check OAuth credentials
if config.OAuth.ConsumerKey == "" {
fmt.Fprintf(os.Stderr, "OAuth credentials not configured\n")
fmt.Println("Run 'xapi login' to configure your credentials")
os.Exit(1)
}
fmt.Println("\nAuthenticating with X API...")
// Make authenticated request to /2/users/me
apiURL := "https://api.x.com/2/users/me"
// Create HTTP client
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err)
os.Exit(1)
}
// Generate OAuth 1.0a Authorization header
oauthClient := NewOAuthClient(config)
authHeader := oauthClient.GetAuthorizationHeader("GET", apiURL, map[string]string{})
req.Header.Set("Authorization", authHeader)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Error making API request: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading response: %v\n", err)
os.Exit(1)
}
// Check status
if resp.StatusCode != http.StatusOK {
fmt.Fprintf(os.Stderr, "\nAUTHENTICATION FAILED\n")
fmt.Fprintf(os.Stderr, "Status: %d\n", resp.StatusCode)
fmt.Fprintf(os.Stderr, "Response: %s\n", string(body))
fmt.Println("\nPlease check your OAuth credentials:")
fmt.Println(" - Consumer Key (API Key)")
fmt.Println(" - Consumer Secret (API Secret)")
fmt.Println(" - Access Token")
fmt.Println(" - Access Token Secret")
fmt.Println("\nRun 'xapi login' to reconfigure")
os.Exit(1)
}
// Parse response
var userResp UserMeResponse
if err := json.Unmarshal(body, &userResp); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
os.Exit(1)
}
// Check for API errors
if len(userResp.Errors) > 0 {
fmt.Println("\nAUTHENTICATION FAILED")
for _, err := range userResp.Errors {
fmt.Printf(" - %s\n", err.Message)
}
os.Exit(1)
}
// Success!
fmt.Println("\nAUTHENTICATION SUCCESSFUL")
fmt.Println("=========================")
fmt.Printf("\nAuthenticated as:\n")
fmt.Printf(" Name: %s\n", userResp.Data.Name)
fmt.Printf(" Username: @%s\n", userResp.Data.Username)
fmt.Printf(" User ID: %s\n", userResp.Data.ID)
fmt.Println("\nYour OAuth credentials are working correctly!")
fmt.Println("You can now use 'xapi search' and 'xapi create'")
fmt.Println()
}
func loadConfig() (*Config, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
configFile := filepath.Join(homeDir, ".config", "xapi", "config.toml")
var config Config
if _, err := toml.DecodeFile(configFile, &config); err != nil {
return nil, err
}
return &config, nil
}