Created
June 18, 2026 08:28
-
-
Save rhnvrm/468798481b64730ba17fa4d8a5811b77 to your computer and use it in GitHub Desktop.
myinstants uvx CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --script | |
| # /// script | |
| # requires-python = ">=3.11" | |
| # /// | |
| # myinstants - tiny MyInstants CLI | |
| # | |
| # Save somewhere on your PATH, then make it executable: | |
| # chmod +x myinstants | |
| # | |
| # Run directly as a uv script: | |
| # ./myinstants search fahhh | |
| # ./myinstants play fahhh | |
| # | |
| # Or install via a symlink: | |
| # mkdir -p ~/.local/bin | |
| # ln -sf "$PWD/myinstants" ~/.local/bin/myinstants | |
| # | |
| # Then use it like this: | |
| # myinstants search vine boom | |
| # myinstants play fahhh | |
| # myinstants play vine boom -n 2 | |
| # myinstants play fahhh --dry-run | |
| # | |
| # Notes: | |
| # - Uses https://myinstants-api.vercel.app/search?q=... | |
| # - Picks the best-ranked match by default | |
| # - Override the player with: MYINSTANTS_PLAYER=mpv myinstants play fahhh | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| from dataclasses import dataclass | |
| from difflib import SequenceMatcher | |
| API_BASE = "https://myinstants-api.vercel.app" | |
| DEFAULT_LIMIT = 10 | |
| @dataclass | |
| class Instant: | |
| id: str | |
| title: str | |
| url: str | |
| mp3: str | |
| class MyInstantsError(RuntimeError): | |
| pass | |
| def api_get(path: str, **params: str) -> dict: | |
| query = urllib.parse.urlencode(params) | |
| url = f"{API_BASE}{path}?{query}" if query else f"{API_BASE}{path}" | |
| request = urllib.request.Request( | |
| url, | |
| headers={ | |
| "User-Agent": "myinstants-cli/0.1 (+https://www.myinstants.com)", | |
| "Accept": "application/json", | |
| }, | |
| ) | |
| try: | |
| with urllib.request.urlopen(request, timeout=20) as response: | |
| return json.load(response) | |
| except urllib.error.HTTPError as exc: | |
| detail = exc.read().decode("utf-8", errors="replace") | |
| raise MyInstantsError(f"API error {exc.code}: {detail}") from exc | |
| except urllib.error.URLError as exc: | |
| raise MyInstantsError(f"Network error: {exc.reason}") from exc | |
| def search_instants(query: str) -> list[Instant]: | |
| payload = api_get("/search", q=query) | |
| data = payload.get("data") or [] | |
| return [Instant(**item) for item in data] | |
| def normalize(text: str) -> str: | |
| return re.sub(r"\s+", " ", re.sub(r"[^a-z0-9]+", " ", text.lower())).strip() | |
| def score(query: str, instant: Instant) -> tuple[float, float]: | |
| q = normalize(query) | |
| title = normalize(instant.title) | |
| if q == title: | |
| return (10.0, 1.0) | |
| if q in title: | |
| return (5.0, len(q) / max(len(title), 1)) | |
| ratio = SequenceMatcher(None, q, title).ratio() | |
| starts = 0.2 if title.startswith(q[:3]) else 0.0 | |
| return (ratio + starts, ratio) | |
| def pick_instant(query: str, results: list[Instant], index: int = 1) -> Instant: | |
| if not results: | |
| raise MyInstantsError(f"No results for {query!r}") | |
| ranked = sorted(results, key=lambda item: score(query, item), reverse=True) | |
| if index < 1 or index > len(ranked): | |
| raise MyInstantsError(f"Choice {index} is out of range; got {len(ranked)} result(s)") | |
| return ranked[index - 1] | |
| def list_results(query: str, limit: int) -> int: | |
| results = search_instants(query) | |
| if not results: | |
| print(f"No results for: {query}", file=sys.stderr) | |
| return 1 | |
| for idx, instant in enumerate(results[:limit], start=1): | |
| print(f"{idx:2}. {instant.title}\n {instant.mp3}") | |
| return 0 | |
| def open_with_system_player(url: str) -> None: | |
| override = os.environ.get("MYINSTANTS_PLAYER") | |
| if override: | |
| subprocess.run([override, url], check=True) | |
| return | |
| candidates: list[tuple[str, list[str]]] = [ | |
| ("mpv", ["mpv", url]), | |
| ("vlc", ["vlc", "--play-and-exit", url]), | |
| ("ffplay", ["ffplay", "-nodisp", "-autoexit", url]), | |
| ("play", ["play", url]), | |
| ("paplay", ["paplay", url]), | |
| ("aplay", ["aplay", url]), | |
| ("afplay", ["afplay", url]), | |
| ("xdg-open", ["xdg-open", url]), | |
| ("open", ["open", url]), | |
| ] | |
| for name, command in candidates: | |
| if shutil.which(name): | |
| subprocess.run(command, check=True) | |
| return | |
| raise MyInstantsError( | |
| "No supported audio player found. Install mpv, vlc, ffplay, or set MYINSTANTS_PLAYER." | |
| ) | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(prog="myinstants", description="Search and play MyInstants sounds") | |
| subparsers = parser.add_subparsers(dest="command", required=True) | |
| search_parser = subparsers.add_parser("search", help="Search for sounds") | |
| search_parser.add_argument("query", nargs="+", help="Search query") | |
| search_parser.add_argument("-n", "--limit", type=int, default=DEFAULT_LIMIT, help="Number of results to print") | |
| play_parser = subparsers.add_parser("play", help="Play the best matching sound") | |
| play_parser.add_argument("query", nargs="+", help="Search query") | |
| play_parser.add_argument("-n", "--choice", type=int, default=1, help="Play the Nth best-ranked result") | |
| play_parser.add_argument("--dry-run", action="store_true", help="Print the selected sound without playing it") | |
| return parser | |
| def main(argv: list[str] | None = None) -> int: | |
| parser = build_parser() | |
| args = parser.parse_args(argv) | |
| query = " ".join(args.query) | |
| try: | |
| if args.command == "search": | |
| return list_results(query, args.limit) | |
| if args.command == "play": | |
| instant = pick_instant(query, search_instants(query), args.choice) | |
| print(f"Playing: {instant.title}") | |
| print(instant.mp3) | |
| if not args.dry_run: | |
| open_with_system_player(instant.mp3) | |
| return 0 | |
| except MyInstantsError as exc: | |
| print(f"error: {exc}", file=sys.stderr) | |
| return 1 | |
| except subprocess.CalledProcessError as exc: | |
| print(f"error: player failed with exit code {exc.returncode}", file=sys.stderr) | |
| return 1 | |
| parser.error("unknown command") | |
| return 2 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment