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.
148 lines
3.8 KiB
Go
148 lines
3.8 KiB
Go
/*
|
|
Copyright © 2025 maxtheweb
|
|
*/
|
|
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// loginCmd represents the login command
|
|
var loginCmd = &cobra.Command{
|
|
Use: "login",
|
|
Short: "Set up your X API credentials easily",
|
|
Long: `Opens your editor to configure your X API credentials.
|
|
|
|
The configuration will be saved to ~/.config/xapi/config.toml
|
|
Get your credentials from: https://developer.x.com/en/portal/dashboard
|
|
|
|
You'll need:
|
|
- OAuth 1.0a credentials (Consumer Key/Secret, Access Token/Secret)
|
|
- OAuth 2.0 credentials (Client ID/Secret - optional, for future features)
|
|
|
|
Usage:
|
|
xapi login # Edit credentials in your editor`,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
runLogin()
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(loginCmd)
|
|
}
|
|
|
|
// findEditor searches for an available text editor
|
|
func findEditor() string {
|
|
// First, check if EDITOR is set
|
|
if editor := os.Getenv("EDITOR"); editor != "" {
|
|
// Verify it exists
|
|
if _, err := exec.LookPath(editor); err == nil {
|
|
return editor
|
|
}
|
|
}
|
|
|
|
// Check for VISUAL (some systems use this)
|
|
if visual := os.Getenv("VISUAL"); visual != "" {
|
|
if _, err := exec.LookPath(visual); err == nil {
|
|
return visual
|
|
}
|
|
}
|
|
|
|
// Try common editors in order of preference
|
|
editors := []string{
|
|
"nvim", // Neovim (power users)
|
|
"vim", // Vim (widely available)
|
|
"vi", // Vi (almost always there)
|
|
"nano", // Nano (beginner-friendly)
|
|
"emacs", // Emacs (for the enlightened)
|
|
"micro", // Micro (modern terminal editor)
|
|
"code", // VS Code (if they have it in terminal)
|
|
"subl", // Sublime Text
|
|
"gedit", // GNOME editor
|
|
"kate", // KDE editor
|
|
}
|
|
|
|
for _, editor := range editors {
|
|
if _, err := exec.LookPath(editor); err == nil {
|
|
return editor
|
|
}
|
|
}
|
|
|
|
return "" // No editor found
|
|
}
|
|
|
|
func runLogin() {
|
|
// Get config directory
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
configDir := filepath.Join(homeDir, ".config", "xapi")
|
|
configFile := filepath.Join(configDir, "config.toml")
|
|
|
|
// Create config directory if it doesn't exist
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating config directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Check if config file exists, if not create template
|
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
|
template := `# X API Configuration
|
|
# Get your credentials from: https://developer.x.com/en/portal/dashboard
|
|
#
|
|
# SECURITY NOTE: Never share this file or commit it to version control!
|
|
|
|
# OAuth 1.0a Credentials (required for posting and searching)
|
|
[oauth]
|
|
consumer_key = ""
|
|
consumer_secret = ""
|
|
access_token = ""
|
|
access_token_secret = ""
|
|
|
|
# OAuth 2.0 Credentials (optional, for future features)
|
|
[oauth2]
|
|
client_id = ""
|
|
client_secret = ""
|
|
`
|
|
if err := os.WriteFile(configFile, []byte(template), 0600); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating config file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Get editor using smart detection
|
|
editor := findEditor()
|
|
if editor == "" {
|
|
fmt.Fprintf(os.Stderr, "Error: No text editor found!\n\n")
|
|
fmt.Println("Please install one of: nvim, vim, vi, nano, emacs")
|
|
fmt.Println("Or set your preferred editor: export EDITOR=nvim")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Open editor
|
|
fmt.Printf("Opening %s with %s...\n", configFile, editor)
|
|
cmd := exec.Command(editor, configFile)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error opening editor: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("\nCredentials saved!")
|
|
fmt.Println("\nNext step:")
|
|
fmt.Println(" xapi status # Test your authentication")
|
|
fmt.Println("\nThen you can use:")
|
|
fmt.Println(" xapi search # Search tweets")
|
|
fmt.Println(" xapi create # Create posts")
|
|
} |