Reputation: 33
I'm working on a Perl program to download torrents using Jackett indexers and torznab APIs. For indexers that provide the magnet link directly in the XML this is super simple, but others provide a link (which appears to be an 'exact source' magnet link after some research)
Goal: Convert this direct link to a 'magnet:?' URL or .torrent file for use with qbittorrent
Here's what I have tried so far:
WWW::Mechanize (calling 'get' on the xs magnet URL)
Error GETing magnet:?<rest of magnet link>: Protocol scheme 'magnet' is not supported
wget
HTTP request sent, awaiting response... 302 Found
Location: magnet:?<rest of magnet link>: Unsupported scheme ‘magnet’.
curl or curl -o
No value returned/blank file
I feel like there is a simple solution to this I just haven't had any success so far, sure I could filter the error message from mech or wget and pull the URL that way but that doesn't seem like the correct way to do this.
Thanks in advance!
Update: It appears this is a limitation of Jackett (or rather the indexers being accessed) in that a direct link cannot be provided through the api.
The solution to this is to take the redirect link from URL headers
curl '$URL' -si | grep -oP 'Location: \K.*'
Which in this case will return the magnet link
Upvotes: 1
Views: 2184
Reputation: 804
here is what i do based on Link attribute provided from jackket api based on status code you can determine if response is magnet uri or a torrent file.
async getMagnetFromURL(
urlString: string,
downloadPath = "./downloads"
): Promise<string> {
// Validate input is a proper URL
let url;
try {
url = new URL(urlString);
} catch (e) {
throw new Error("Invalid URL provided");
}
return new Promise((resolve, reject) => {
// Only allow HTTPS requests for security
http.get(url, async (response) => {
// Follow redirects up to a reasonable limit
logger.info(`get magnet status code`, response.statusCode);
if (response.statusCode === 301 || response.statusCode === 302) {
if (response.headers.location) {
logger.info("got magnet uri");
return resolve(response.headers.location);
}
}
if (response.statusCode === 200) {
logger.info("got torrent file");
// Create download directory if it doesn't exist
await fs.mkdirSync(downloadPath, { recursive: true });
// Generate unique filename
const timestamp = Date.now();
const filename = `download_${timestamp}.torrent`;
const filepath = path.join(downloadPath, filename);
// Create write stream
const fileStream = fs.createWriteStream(filepath);
// Pipe response to file
response.pipe(fileStream);
fileStream.on("finish", () => {
logger.info("torrent downloaded:", filepath);
fileStream.close();
resolve(filepath);
});
fileStream.on("error", (err) => {
logger.info("torrent saving got error:", err);
fs.unlinkSync(filepath);
reject(err);
});
} else {
logger.info("torrent saving got generral error");
reject(new Error(`Unexpected status code: ${response.statusCode}`));
}
});
});
}
Upvotes: 0
Reputation: 132768
That's not really how the protocol works. And, it's not simple.
The magnet scheme addresses content (so, that's the hashes you'll see) instead of a single location. You have to give that hash to something for it to return locations (multiple) where you can grab chunks of the content. Something like Mojo::Transmission might help. Searching MetaCPAN for BitTorrent has plenty of hits.
There are many command line programs that might do what you want.
Upvotes: 1