Created
April 20, 2026 01:38
-
-
Save vpnry/114a68c44d1e3931a9398d3e6a827047 to your computer and use it in GitHub Desktop.
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 python3 | |
| """ | |
| s.id URL Shortener CLI | |
| Usage: python sid.py <command> [options] | |
| Env vars required: | |
| SID_AUTH_ID - your X-Auth-Id | |
| SID_AUTH_KEY - your X-Auth-Key | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import argparse | |
| import urllib.request | |
| import urllib.error | |
| BASE_URL = "https://api.s.id/v1" | |
| def get_headers(): | |
| auth_id = os.environ.get("SID_AUTH_ID") | |
| auth_key = os.environ.get("SID_AUTH_KEY") | |
| if not auth_id or not auth_key: | |
| print("Error: SID_AUTH_ID and SID_AUTH_KEY must be set in your environment.") | |
| print(" export SID_AUTH_ID=your_id") | |
| print(" export SID_AUTH_KEY=your_key") | |
| sys.exit(1) | |
| return { | |
| "X-Auth-Id": auth_id, | |
| "X-Auth-Key": auth_key, | |
| "Content-Type": "application/json", | |
| "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", | |
| } | |
| def api_request(method, path, data=None): | |
| url = BASE_URL + path | |
| headers = get_headers() | |
| body = json.dumps(data).encode() if data else None | |
| req = urllib.request.Request(url, data=body, headers=headers, method=method) | |
| try: | |
| with urllib.request.urlopen(req) as resp: | |
| return json.loads(resp.read().decode()) | |
| except urllib.error.HTTPError as e: | |
| body = e.read().decode() | |
| try: | |
| err = json.loads(body) | |
| print(f"API Error {e.code}: {err.get('message', body)}") | |
| except Exception: | |
| print(f"HTTP Error {e.code}: {body}") | |
| sys.exit(1) | |
| def print_link(data): | |
| print(f" ID : {data.get('id')}") | |
| print(f" Short : https://s.id/{data.get('short')}") | |
| print(f" URL : {data.get('long_url')}") | |
| if data.get('title'): | |
| print(f" Title : {data.get('title')}") | |
| # --- Commands --- | |
| def cmd_create(args): | |
| """Create a new short link.""" | |
| payload = {"long_url": args.url} | |
| result = api_request("POST", "/links", payload) | |
| data = result.get("data", {}) | |
| print("✓ Link created:") | |
| print_link(data) | |
| if args.short or args.title: | |
| link_id = data.get("id") | |
| update_payload = {"long_url": args.url} | |
| if args.short: | |
| update_payload["short"] = args.short | |
| if args.title: | |
| update_payload["title"] = args.title | |
| result2 = api_request("POST", f"/links/{link_id}", update_payload) | |
| data2 = result2.get("data", {}) | |
| print("✓ Link updated with custom short/title:") | |
| print_link(data2) | |
| def cmd_update(args): | |
| """Update an existing link by numeric ID.""" | |
| # long_url is required by the API — auto-fetch existing value if not provided | |
| if args.url: | |
| long_url = args.url | |
| else: | |
| existing = api_request("GET", f"/links/{args.id}") | |
| long_url = existing.get("data", {}).get("long_url") | |
| if not long_url: | |
| print("Could not fetch existing long_url. Please pass --url explicitly.") | |
| sys.exit(1) | |
| payload = {"long_url": long_url} | |
| if args.short: | |
| payload["short"] = args.short | |
| if args.title: | |
| payload["title"] = args.title | |
| result = api_request("POST", f"/links/{args.id}", payload) | |
| print("✓ Link updated:") | |
| print_link(result.get("data", {})) | |
| def cmd_get(args): | |
| """Get link detail by short code.""" | |
| result = api_request("GET", f"/links/short/{args.short}") | |
| print("Link detail:") | |
| print_link(result.get("data", {})) | |
| def cmd_list(args): | |
| """List all your links.""" | |
| path = "/links" | |
| if args.page: | |
| path += f"?page={args.page}" | |
| result = api_request("GET", path) | |
| items = result.get("data", []) | |
| if not items: | |
| print("No links found.") | |
| return | |
| for item in items: | |
| print(f" [{item.get('id')}] s.id/{item.get('short')} -> {item.get('long_url')}") | |
| def cmd_archive(args): | |
| """Archive a link by numeric ID.""" | |
| api_request("PUT", f"/links/{args.id}/archive") | |
| print(f"✓ Link {args.id} archived.") | |
| def cmd_unarchive(args): | |
| """Unarchive a link by numeric ID.""" | |
| api_request("PUT", f"/links/{args.id}/unarchive") | |
| print(f"✓ Link {args.id} unarchived.") | |
| def cmd_quota(args): | |
| """Show your API quota.""" | |
| result = api_request("GET", "/quota") | |
| data = result.get("data", {}) | |
| for k, v in data.items(): | |
| print(f" {k}: {v}") | |
| def cmd_me(args): | |
| """Show your account info.""" | |
| result = api_request("GET", "/me") | |
| data = result.get("data", {}) | |
| for k, v in data.items(): | |
| print(f" {k}: {v}") | |
| # --- Main --- | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="s.id URL Shortener CLI", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| # Create a short link (random short code) | |
| python sid.py create https://tipitakapali.org | |
| # Create with custom short code and title | |
| python sid.py create https://tipitakapali.org --short tipitaka --title "Tipitaka Pali" | |
| # Update link by ID (long_url auto-fetched if omitted) | |
| python sid.py update 36304710 --short tipitaka --title "Chaṭṭha Saṅgāyana Tipiṭaka" | |
| # Get link detail by short code | |
| python sid.py get tipitaka | |
| # List all links | |
| python sid.py list | |
| # Archive / unarchive | |
| python sid.py archive 36304710 | |
| python sid.py unarchive 36304710 | |
| # Show quota and account info | |
| python sid.py quota | |
| python sid.py me | |
| """ | |
| ) | |
| sub = parser.add_subparsers(dest="command") | |
| sub.required = True | |
| # create | |
| p = sub.add_parser("create", help="Create a new short link") | |
| p.add_argument("url", help="The long URL to shorten") | |
| p.add_argument("--short", help="Custom short code (e.g. tipitaka)") | |
| p.add_argument("--title", help="Title for the link") | |
| p.set_defaults(func=cmd_create) | |
| # update | |
| p = sub.add_parser("update", help="Update a link by numeric ID") | |
| p.add_argument("id", help="Numeric link ID") | |
| p.add_argument("--url", help="New long URL (auto-fetched if omitted)") | |
| p.add_argument("--short", help="New short code") | |
| p.add_argument("--title", help="New title") | |
| p.set_defaults(func=cmd_update) | |
| # get | |
| p = sub.add_parser("get", help="Get link detail by short code") | |
| p.add_argument("short", help="Short code (e.g. tipitaka)") | |
| p.set_defaults(func=cmd_get) | |
| # list | |
| p = sub.add_parser("list", help="List all your links") | |
| p.add_argument("--page", help="Page number", default=None) | |
| p.set_defaults(func=cmd_list) | |
| # archive | |
| p = sub.add_parser("archive", help="Archive a link") | |
| p.add_argument("id", help="Numeric link ID") | |
| p.set_defaults(func=cmd_archive) | |
| # unarchive | |
| p = sub.add_parser("unarchive", help="Unarchive a link") | |
| p.add_argument("id", help="Numeric link ID") | |
| p.set_defaults(func=cmd_unarchive) | |
| # quota | |
| p = sub.add_parser("quota", help="Show API quota") | |
| p.set_defaults(func=cmd_quota) | |
| # me | |
| p = sub.add_parser("me", help="Show account info") | |
| p.set_defaults(func=cmd_me) | |
| args = parser.parse_args() | |
| args.func(args) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment