Skip to main content
ClaudeWave
Skill122 repo starsupdated 26d ago

torrent-search

Search for torrents by title or IMDB ID via a Torznab-compatible API. Use when: (1) User asks to find a torrent for a movie or show, (2) You need a magnet link for a given title, or (3) User provides an IMDB ID and wants download options.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/besoeasy/open-skills /tmp/torrent-search && cp -r /tmp/torrent-search/skills/torrent-search ~/.claude/skills/torrent-search
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Torrent Search

Search any Torznab-compatible indexer (e.g. bitmagnet) for torrents by title or IMDB ID. Returns magnet links, file sizes, seeders, resolution, and codec.

## When to use

- User asks to find a torrent for a movie, TV show, or any other content
- User provides an IMDB ID (e.g. `tt1234567`) and wants download options
- You need to programmatically retrieve a magnet link for a given title
- User asks to compare available qualities (720p, 1080p, 2160p) for a release

## Required tools / APIs

- `curl` — HTTP requests (pre-installed on most systems)
- `jq` — JSON parsing (used after XML→JSON conversion)
- `xmllint` — XML parsing (optional, from `libxml2-utils`)
- A running Torznab endpoint — examples use `https://bitmagnetfortheweebs.midnightignite.me/torznab/api`

Install options:

```bash
# Ubuntu/Debian
sudo apt-get install -y curl jq libxml2-utils

# macOS
brew install curl jq libxml2

# Node.js (no extra packages — uses native fetch + DOMParser via fast-xml-parser)
npm install fast-xml-parser
```

## Skills

### search_by_title

Search for torrents using a free-text title query.

```bash
TORZNAB_URL="https://bitmagnetfortheweebs.midnightignite.me/torznab/api"
QUERY="Breaking Bad"

curl -fsS --max-time 15 \
  "${TORZNAB_URL}?t=search&q=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")" \
  | xmllint --xpath "//item" - 2>/dev/null \
  | grep -oP '(?<=<title>).*?(?=</title>)'
```

Full extraction with magnet links:

```bash
TORZNAB_URL="https://bitmagnetfortheweebs.midnightignite.me/torznab/api"
QUERY="Inception 2010"

xml=$(curl -fsS --max-time 15 \
  "${TORZNAB_URL}?t=search&q=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")")

# Print title + magnet for each result
echo "$xml" | python3 - << 'EOF'
import sys, xml.etree.ElementTree as ET

data = sys.stdin.read()
root = ET.fromstring(data)
ns = {'torznab': 'http://torznab.com/schemas/2015/feed'}

for item in root.findall('.//item'):
    title = item.findtext('title', '')
    size  = item.findtext('size', '0')
    enc   = item.find('enclosure')
    magnet = enc.get('url') if enc is not None else ''

    attrs = {a.get('name'): a.get('value') for a in item.findall('torznab:attr', ns)}
    seeders    = attrs.get('seeders', '?')
    resolution = attrs.get('resolution', '')
    codec      = attrs.get('video', '')

    size_gb = round(int(size) / 1_073_741_824, 2)
    print(f"{title}")
    print(f"  Size: {size_gb} GB  Seeders: {seeders}  {resolution} {codec}")
    print(f"  Magnet: {magnet[:80]}...")
    print()
EOF
```

**Node.js:**

```javascript
import { XMLParser } from 'fast-xml-parser';

const TORZNAB_URL = 'https://bitmagnetfortheweebs.midnightignite.me/torznab/api';

async function searchTorrents(query) {
  const url = `${TORZNAB_URL}?t=search&q=${encodeURIComponent(query)}`;
  const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  const xml = await res.text();
  const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
  const doc = parser.parse(xml);

  const items = doc?.rss?.channel?.item ?? [];
  const list = Array.isArray(items) ? items : [items];

  return list.map(item => {
    const attrs = {};
    const rawAttrs = item['torznab:attr'] ?? [];
    const attrList = Array.isArray(rawAttrs) ? rawAttrs : [rawAttrs];
    for (const a of attrList) attrs[a['@_name']] = a['@_value'];

    return {
      title:      item.title,
      sizeBytes:  Number(item.size ?? 0),
      sizeGB:     +(Number(item.size ?? 0) / 1_073_741_824).toFixed(2),
      magnet:     item.enclosure?.['@_url'] ?? attrs.magneturl ?? '',
      infohash:   attrs.infohash ?? '',
      seeders:    Number(attrs.seeders ?? 0),
      leechers:   Number(attrs.leechers ?? 0),
      resolution: attrs.resolution ?? '',
      codec:      attrs.video ?? '',
      year:       attrs.year ?? '',
      imdb:       attrs.imdb ? `tt${attrs.imdb}` : '',
    };
  });
}

// Usage
searchTorrents('Inception 2010').then(results => {
  results.forEach(r => {
    console.log(`${r.title}`);
    console.log(`  ${r.sizeGB} GB | ${r.resolution} ${r.codec} | ${r.seeders} seeders`);
    console.log(`  ${r.magnet.slice(0, 80)}...`);
    console.log();
  });
});
```

---

### search_by_imdb_id

Search by exact IMDB ID to get all available releases for a specific title.

```bash
TORZNAB_URL="https://bitmagnetfortheweebs.midnightignite.me/torznab/api"
IMDB_ID="tt12735488"   # Kalki 2898 AD

curl -fsS --max-time 15 \
  "${TORZNAB_URL}?t=search&q=${IMDB_ID}" \
  | python3 - << 'EOF'
import sys, xml.etree.ElementTree as ET

root = ET.fromstring(sys.stdin.read())
ns = {'torznab': 'http://torznab.com/schemas/2015/feed'}

for item in root.findall('.//item'):
    title = item.findtext('title', '')
    size  = int(item.findtext('size', '0'))
    enc   = item.find('enclosure')
    magnet = enc.get('url') if enc is not None else ''
    attrs = {a.get('name'): a.get('value') for a in item.findall('torznab:attr', ns)}

    print(f"[{attrs.get('resolution','?'):6}] {round(size/1e9,1):5.1f}GB  "
          f"S:{attrs.get('seeders','?'):>3}  {title}")
EOF
```

**Node.js:**

```javascript
import { XMLParser } from 'fast-xml-parser';

const TORZNAB_URL = 'https://bitmagnetfortheweebs.midnightignite.me/torznab/api';

async function searchByImdb(imdbId) {
  // imdbId format: 'tt1234567' or just '1234567'
  const id = imdbId.startsWith('tt') ? imdbId : `tt${imdbId}`;
  const url = `${TORZNAB_URL}?t=search&q=${id}`;

  const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  const xml = await res.text();
  const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
  const doc = parser.parse(xml);

  const items = doc?.rss?.channel?.item ?? [];
  const list = Array.isArray(items) ? items : [items];

  return list.map(item => {
    con