Files
magnus919_agent-skills/lastfm/scripts/lastfm-cli
T

81 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Small, dependency-free Last.fm API client used by the lastfm skill."""
import argparse
import json
import os
import sys
import urllib.parse
import urllib.request
API = "https://ws.audioscrobbler.com/2.0/"
METHODS = {
"user info": "user.getinfo", "user recent-tracks": "user.getrecenttracks",
"user top-artists": "user.gettopartists", "user top-tracks": "user.gettoptracks",
"user loved-tracks": "user.getlovedtracks", "user friends": "user.getfriends",
"user weekly-charts": "user.getweeklychartlist", "artist similar": "artist.getsimilar",
"artist info": "artist.getinfo", "artist top-tracks": "artist.gettoptracks",
"artist search": "artist.search", "album info": "album.getinfo", "album search": "album.search",
"track similar": "track.getsimilar", "track search": "track.search",
"tag top-artists": "tag.gettopartists", "tag top-tracks": "tag.gettoptracks",
"tag top-albums": "tag.gettopalbums", "chart top-artists": "chart.gettopartists",
"geo top-artists": "geo.gettopartists", "geo top-tracks": "geo.gettoptracks",
}
def parser():
p = argparse.ArgumentParser(description="Last.fm API client", epilog="Examples: lastfm-cli artist similar Radiohead --limit 5")
p.add_argument("--json", action="store_true", help="emit JSON")
p.add_argument("--dry-run", action="store_true", help="show the API plan without a request")
subs = p.add_subparsers(dest="group", required=True)
for group, commands in {"user": ["info", "recent-tracks", "top-artists", "top-tracks", "loved-tracks", "friends", "weekly-charts"], "artist": ["similar", "info", "top-tracks", "search"], "album": ["info", "search"], "track": ["similar", "search", "scrobble", "now-playing", "love"], "tag": ["top-artists", "top-tracks", "top-albums"], "chart": ["top-artists"], "geo": ["top-artists", "top-tracks"], "auth": ["get-token", "get-session"]}.items():
gp = subs.add_parser(group)
cs = gp.add_subparsers(dest="command", required=True)
for command in commands:
cp = cs.add_parser(command)
if group in {"user", "artist", "album", "tag", "geo"} or group == "track":
cp.add_argument("values", nargs="*", metavar="VALUE", help="username, artist, tag, country, or track values")
cp.add_argument("--limit", type=int)
cp.add_argument("--period")
cp.add_argument("--autocorrect", action="store_true")
return p
def main():
args = parser().parse_args()
key = f"{args.group} {args.command}"
if args.group == "auth":
print("Auth operations require LASTFM_API_KEY, LASTFM_API_SECRET, and a browser authorization flow.")
return 0
values = getattr(args, "values", [])
if args.command not in {"search", "top-artists", "top-tracks", "top-albums", "weekly-charts"} and not values:
parser().error(f"{args.group} {args.command}: a value is required")
method = METHODS.get(key)
if not method:
parser().error(f"unsupported operation: {key}")
params = {"method": method, "format": "json"}
if values:
param = "user" if args.group == "user" else "country" if args.group == "geo" else "tag" if args.group == "tag" else "artist"
params[param] = values[0]
if args.group in {"track", "album"} and len(values) > 1:
params["track" if args.group == "track" else "album"] = values[1]
for option in ("limit", "period", "autocorrect"):
value = getattr(args, option, None)
if value not in (None, False): params[option] = value
if args.dry_run:
print(json.dumps({"dry_run": True, "url": API, "params": params}, indent=2))
return 0
api_key = os.getenv("LASTFM_API_KEY")
if not api_key:
print("LASTFM_API_KEY is required", file=sys.stderr)
return 2
params["api_key"] = api_key
try:
with urllib.request.urlopen(f"{API}?{urllib.parse.urlencode(params)}", timeout=30) as response:
payload = json.load(response)
except Exception as exc:
print(f"Last.fm request failed: {exc}", file=sys.stderr)
return 1
print(json.dumps(payload, indent=2) if args.json else json.dumps(payload, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())