Files
songbox/songbox/__main__.py
ポキ 161a4d42d6 refactor(clients): reorganize client modules into clients package
- 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
2025-06-07 06:57:01 -07:00

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()