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.
npx skills add https://github.com/besoeasy/open-skills --skill 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.
tt1234567) and wants download optionscurl — HTTP requests (pre-installed on most systems)jq — JSON parsing (used after XML→JSON conversion)xmllint — XML parsing (optional, from libxml2-utils)https://bitmagnetfortheweebs.midnightignite.me/torznab/apiInstall options:
# 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
Search for torrents using a free-text title query.
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:
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:
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 exact IMDB ID to get all available releases for a specific title.
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:
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 => {
const attrs = {};
const rawAttrs = item['torznab:attr'] ?? [];
for (const a of Array.isArray(rawAttrs) ? rawAttrs : [rawAttrs]) {
attrs[a['@_name']] = a['@_value'];
}
return {
title: item.title,
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),
resolution: attrs.resolution ?? '',
codec: attrs.video ?? '',
year: attrs.year ?? '',
};
});
}
// Usage — find all releases for a specific IMDB title
searchByImdb('tt12735488').then(results => {
// Sort by seeders descending, then by size descending
results.sort((a, b) => b.seeders - a.seeders || b.sizeGB - a.sizeGB);
results.forEach(r =>
console.log(`[${r.resolution || '?':>6}] ${r.sizeGB}GB S:${r.seeders} ${r.title}`)
);
});
Filter and rank results by quality preference (resolution priority + seeder count).
Node.js:
function pickBest(results, { preferResolution = '1080p', minSeeders = 1 } = {}) {
const resolutionRank = { '2160p': 4, '1080p': 3, '720p': 2, '480p': 1 };
const preferred = resolutionRank[preferResolution] ?? 3;
return results
.filter(r => r.seeders >= minSeeders)
.sort((a, b) => {
const ra = resolutionRank[a.resolution] ?? 0;
const rb = resolutionRank[b.resolution] ?? 0;
// Exact preferred resolution first, then by seeders
const aMatch = ra === preferred ? 1 : 0;
const bMatch = rb === preferred ? 1 : 0;
if (bMatch !== aMatch) return bMatch - aMatch;
return b.seeders - a.seeders;
})[0] ?? null;
}
// Usage
const results = await searchByImdb('tt12735488');
const best = pickBest(results, { preferResolution: '1080p', minSeeders: 1 });
if (best) {
console.log(`Best pick: ${best.title}`);
console.log(`Magnet: ${best.magnet}`);
}
Each result object contains:
title — string — release name as indexed (e.g. "Inception.2010.1080p.BluRay.x264")sizeGB — number — file size in gigabytes (e.g. 3.15)magnet — string — full magnet URI starting with magnet:?xt=urn:btih:...infohash — string — 40-char hex SHA-1 info hashseeders — number — active seeders at index time (may be stale)leechers — number — active leechers at index timeresolution — string — "720p", "1080p", "2160p", or "" if unknowncodec — string — "x264", "x265", "XviD", "AV1", or "" if unknownyear — string — release year (e.g. "2024")imdb — string — IMDB ID in tt format (e.g. "tt12735488")Error shape:
{ "error": "HTTP 503", "fix": "Indexer is down — retry in 30s or use a different endpoint" }
?q=tt...) is more precise than title search — prefer it when you have the IDYou have torrent-search capability via a Torznab API.
When a user asks to find a torrent for something:
1. If they provide an IMDB ID (tt...), use search_by_imdb_id for precise results.
2. Otherwise use search_by_title with the title and year if known.
3. Parse the XML response and extract: title, sizeGB, seeders, resolution, magnet link.
4. Sort results by seeders descending. Filter out 0-seeder results unless nothing else exists.
5. Present the top 3–5 results with: title, size, resolution, seeder count.
6. Ask the user which one they want, then return the full magnet link.
7. Never auto-start a download without user confirmation.
Torznab endpoint: https://bitmagnetfortheweebs.midnightignite.me/torznab/api
Empty results / no <item> elements:
<channel> but no <item> childrenxmllint not found:
xmllint: command not foundsudo apt-get install -y libxml2-utils (Linux) or brew install libxml2 (macOS). Alternatively use the Python xml.etree approach which needs no extra tools.fast-xml-parser not available:
Cannot find module 'fast-xml-parser'npm install fast-xml-parser. As a zero-dependency alternative, use the Bash + Python script path which only needs the standard library.Endpoint unreachable (connection refused / timeout):
curl: (7) Failed to connect or curl: (28) Operation timed outdocker run -d ghcr.io/bitmagnet-io/bitmagnet) and point TORZNAB_URL to your local instance.Magnet link is empty:
enclosure url is missing or blank for some itemsmagnet:?xt=urn:btih:<infohash>&dn=<encoded-title>Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take besoeasy/torrent-search from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, brew, apt, docker.
Without those the skill loads but fails at the first command.