- Move auth, user, and music clients into new clients package - Update imports to use new module paths - Add __init__.py to expose clients from package - Clean up code formatting and imports - Add pre-commit configuration for code quality checks
331 lines
11 KiB
Python
331 lines
11 KiB
Python
"""Command-line interface for songbox API Client"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
from .client import SongboxClient
|
|
from .exceptions import AuthenticationError, songboxError
|
|
|
|
|
|
def create_parser() -> argparse.ArgumentParser:
|
|
"""Create the argument parser for the CLI."""
|
|
parser = argparse.ArgumentParser(
|
|
prog="songbox",
|
|
description="songbox API Client - Access songbox music streaming API from command line",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Login and get user info
|
|
songbox auth login --country-code 960 --mobile 7777777
|
|
songbox auth verify --otp 123456 --verify-id <verify_id>
|
|
songbox user me
|
|
|
|
# Search for music
|
|
songbox music search "huttaa"
|
|
|
|
# Get album information
|
|
songbox music album 808
|
|
|
|
# Download a song
|
|
songbox music download "https://example.com/song.mp3"
|
|
songbox music download "https://example.com/song.mp3" --path ./my_music
|
|
|
|
# Get user's following list
|
|
songbox user following
|
|
""",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--token",
|
|
help="Authentication token (can also be set via songbox_TOKEN env var)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--base-url",
|
|
default="https://lavafoshi.online",
|
|
help="Base URL for the API (default: https://lavafoshi.online)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--format",
|
|
choices=["json", "table", "simple"],
|
|
default="simple",
|
|
help="Output format (default: simple)",
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
|
|
# Auth commands
|
|
auth_parser = subparsers.add_parser("auth", help="Authentication commands")
|
|
auth_subparsers = auth_parser.add_subparsers(dest="auth_command")
|
|
|
|
# Login command
|
|
login_parser = auth_subparsers.add_parser(
|
|
"login", help="Initiate login with mobile number"
|
|
)
|
|
login_parser.add_argument(
|
|
"--country-code", required=True, help="Country code (e.g., 960)"
|
|
)
|
|
login_parser.add_argument("--mobile", required=True, help="Mobile number")
|
|
|
|
# Verify OTP command
|
|
verify_parser = auth_subparsers.add_parser(
|
|
"verify", help="Verify OTP and get token"
|
|
)
|
|
verify_parser.add_argument("--otp", required=True, help="OTP code")
|
|
verify_parser.add_argument(
|
|
"--verify-id", required=True, help="Verification ID from login"
|
|
)
|
|
|
|
# Refresh token command
|
|
auth_subparsers.add_parser("refresh", help="Refresh authentication token")
|
|
|
|
# Logout command
|
|
auth_subparsers.add_parser("logout", help="Logout (clear token)")
|
|
|
|
# User commands
|
|
user_parser = subparsers.add_parser("user", help="User commands")
|
|
user_subparsers = user_parser.add_subparsers(dest="user_command")
|
|
|
|
user_subparsers.add_parser("me", help="Get current user information")
|
|
user_subparsers.add_parser("following", help="Get following list")
|
|
|
|
# Music commands
|
|
music_parser = subparsers.add_parser("music", help="Music commands")
|
|
music_subparsers = music_parser.add_subparsers(dest="music_command")
|
|
|
|
# Search command
|
|
search_parser = music_subparsers.add_parser("search", help="Search for music")
|
|
search_parser.add_argument("query", help="Search query")
|
|
|
|
# Album command
|
|
album_parser = music_subparsers.add_parser("album", help="Get album information")
|
|
album_parser.add_argument("album_id", help="Album ID")
|
|
|
|
# Song command
|
|
song_parser = music_subparsers.add_parser("song", help="Get song information")
|
|
song_parser.add_argument("song_id", help="Song ID")
|
|
|
|
# Artist command
|
|
artist_parser = music_subparsers.add_parser("artist", help="Get artist information")
|
|
artist_parser.add_argument("artist_id", help="Artist ID")
|
|
|
|
# Trending command
|
|
trending_parser = music_subparsers.add_parser("trending", help="Get trending music")
|
|
trending_parser.add_argument("--limit", type=int, help="Limit number of results")
|
|
|
|
# Download command
|
|
download_parser = music_subparsers.add_parser("download", help="Download a song")
|
|
download_parser.add_argument(
|
|
"song_url", help="Song URL to download (e.g., from song['url_original'])"
|
|
)
|
|
download_parser.add_argument(
|
|
"--path", help="Download directory path (default: ./downloads)"
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def format_output(data, format_type: str) -> str:
|
|
"""Format output based on the specified format."""
|
|
if format_type == "json":
|
|
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
elif format_type == "table":
|
|
# Simple table format for lists
|
|
if isinstance(data, list) and data:
|
|
if isinstance(data[0], dict):
|
|
# Print headers
|
|
headers = list(data[0].keys())
|
|
result = "\t".join(headers) + "\n"
|
|
# Print rows
|
|
for item in data:
|
|
row = [str(item.get(h, "")) for h in headers]
|
|
result += "\t".join(row) + "\n"
|
|
return result
|
|
return str(data)
|
|
else:
|
|
if isinstance(data, dict):
|
|
result = ""
|
|
for key, value in data.items():
|
|
result += f"{key}: {value}\n"
|
|
return result
|
|
elif isinstance(data, list):
|
|
result = ""
|
|
for i, item in enumerate(data):
|
|
if isinstance(item, dict):
|
|
result += f"Item {i + 1}:\n"
|
|
for key, value in item.items():
|
|
result += f" {key}: {value}\n"
|
|
result += "\n"
|
|
else:
|
|
result += f"{i + 1}. {item}\n"
|
|
return result
|
|
else:
|
|
return str(data)
|
|
|
|
|
|
def handle_auth_commands(client: SongboxClient, args) -> None:
|
|
"""Handle authentication commands."""
|
|
if args.auth_command == "login":
|
|
try:
|
|
verify_id = client.auth.login(args.country_code, args.mobile)
|
|
print(f"OTP sent! Verification ID: {verify_id}")
|
|
print(
|
|
f"Use this command to verify: songbox auth verify --otp <OTP> --verify-id {verify_id}"
|
|
)
|
|
except Exception as e:
|
|
print(f"Login failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.auth_command == "verify":
|
|
try:
|
|
token = client.auth.verify_otp(args.otp, args.verify_id)
|
|
print(f"Authentication successful!")
|
|
print(f"Token: {token}")
|
|
print(
|
|
f"Save this token and use it with --token flag or songbox_TOKEN env var"
|
|
)
|
|
except Exception as e:
|
|
print(f"Verification failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.auth_command == "refresh":
|
|
try:
|
|
new_token = client.auth.refresh_token()
|
|
print(f"Token refreshed successfully!")
|
|
print(f"New token: {new_token}")
|
|
except Exception as e:
|
|
print(f"Token refresh failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.auth_command == "logout":
|
|
client.auth.logout()
|
|
print("Logged out successfully!")
|
|
|
|
|
|
def handle_user_commands(client: SongboxClient, args) -> None:
|
|
"""Handle user commands."""
|
|
if args.user_command == "me":
|
|
try:
|
|
user_info = client.user.get_me()
|
|
print(format_output(user_info, args.format))
|
|
except Exception as e:
|
|
print(f"Failed to get user info: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.user_command == "following":
|
|
try:
|
|
following = client.user.get_following()
|
|
if not following:
|
|
print("Not following anyone yet.")
|
|
else:
|
|
print(format_output(following, args.format))
|
|
except Exception as e:
|
|
print(f"Failed to get following list: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def handle_music_commands(client: SongboxClient, args) -> None:
|
|
"""Handle music commands."""
|
|
if args.music_command == "search":
|
|
try:
|
|
results = client.music.search(args.query)
|
|
if not results:
|
|
print(f"No results found for '{args.query}'")
|
|
else:
|
|
print(format_output(results, args.format))
|
|
except Exception as e:
|
|
print(f"Search failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.music_command == "album":
|
|
try:
|
|
album = client.music.get_album(args.album_id)
|
|
print(format_output(album, args.format))
|
|
except Exception as e:
|
|
print(f"Failed to get album: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.music_command == "song":
|
|
try:
|
|
song = client.music.get_song(args.song_id)
|
|
print(format_output(song, args.format))
|
|
except Exception as e:
|
|
print(f"Failed to get song: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.music_command == "artist":
|
|
try:
|
|
artist = client.music.get_artist(args.artist_id)
|
|
print(format_output(artist, args.format))
|
|
except Exception as e:
|
|
print(f"Failed to get artist: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
elif args.music_command == "download":
|
|
try:
|
|
file_path = client.music.download_song(args.song_url, args.path)
|
|
print(f"\nDownload completed successfully!")
|
|
print(f"File saved to: {file_path}")
|
|
except Exception as e:
|
|
print(f"Download failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
"""Main CLI entry point."""
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
# Get token from args or environment
|
|
import os
|
|
|
|
token = args.token or os.getenv("songbox_TOKEN")
|
|
|
|
# Create client
|
|
try:
|
|
client = SongboxClient(base_url=args.base_url)
|
|
|
|
# Set token if provided
|
|
if token:
|
|
client.set_token(token)
|
|
|
|
# Handle commands
|
|
if args.command == "auth":
|
|
handle_auth_commands(client, args)
|
|
elif args.command == "user":
|
|
if not client.is_authenticated:
|
|
print(
|
|
"Authentication required. Please login first or provide a token.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
handle_user_commands(client, args)
|
|
elif args.command == "music":
|
|
if not client.is_authenticated:
|
|
print(
|
|
"Authentication required. Please login first or provide a token.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
handle_music_commands(client, args)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nOperation cancelled.", file=sys.stderr)
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"Error: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
finally:
|
|
if "client" in locals():
|
|
client.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|