Shivabelieva
Shivabelieva

Reputation: 33

Converting Jackett api direct URL to magnet URL

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

Answers (2)

moein rahimi
moein rahimi

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

brian d foy
brian d foy

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

Related Questions