Skip to content

Commit 23246f7

Browse files
committed
feat: add getMagnetLink function
closed: #9
1 parent cd2c59d commit 23246f7

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ $source = $torrent->getSource();
161161
$private = $torrent->isPrivate(); // true or false
162162
$torrent->setPrivate(true);
163163

164+
$magnetLink = $torrent->getMagnetLink();
165+
164166
// 5. Work with torrent, it will try to parse torrent ( cost time )
165167
$torrent->setParseValidator(function ($filename, $paths) {
166168
/**
@@ -200,12 +202,12 @@ $torrent
200202
['https://example.com/announce'],
201203
['https://example1.com/announce']
202204
])
203-
->setSouce('example.com')
205+
->setSource('example.com')
204206
->setPrivate(true);
205207

206208
// Note 2: parse method may fail when get a deep invalid torrent, so it can wrapper like this
207209
try {
208-
$torrent = TorrentFile::load($_POST['torrent']);
210+
$torrent = TorrentFile::load($_POST['torrent']['tmp_name']);
209211
$torrent/** ->setParseValidator(function () {}) */->parse();
210212
} catch (ParseException $e) {
211213
// do something to notice user.

src/TorrentFile.php

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ protected function getInfoString()
271271

272272
public function getInfoField($field, $default = null)
273273
{
274-
return isset($this->data['info'][$field]) ? $this->data['info'][$field] : $default;
274+
return $this->data['info'][$field] ?? $default;
275275
}
276276

277277
public function setInfoField($field, $value)
@@ -486,6 +486,56 @@ public function setPrivate($private)
486486
return $private ? $this->setInfoField('private', 1) : $this->unsetInfoField('private');
487487
}
488488

489+
/**
490+
* Get Torrent Magnet URI
491+
* @return string
492+
*/
493+
public function getMagnetLink($dn = true, $tr = true)
494+
{
495+
$urlSearchParams = [];
496+
497+
$infoHashV1 = $this->getInfoHashV1();
498+
if ($infoHashV1) {
499+
$urlSearchParams[] = 'xt=urn:btih:' . $infoHashV1;
500+
}
501+
502+
$infoHashV2 = $this->getInfoHashV2();
503+
if ($infoHashV2) {
504+
$urlSearchParams[] = 'xt=url:btmh:' . '1220' . $infoHashV2; // 1220 is magic number
505+
}
506+
507+
if ($dn) {
508+
$name = $this->getName() ?? '';
509+
if ($name !== '') {
510+
$urlSearchParams[] = 'dn=' . rawurlencode($name);
511+
}
512+
}
513+
514+
if ($tr) {
515+
$trackers = [];
516+
517+
$announceList = $this->getAnnounceList();
518+
if ($announceList) {
519+
foreach ($announceList as $tier) {
520+
foreach ($tier as $tracker) {
521+
$trackers[] = $tracker;
522+
}
523+
}
524+
} else {
525+
$rootTracker = $this->getAnnounce();
526+
if ($rootTracker) {
527+
$trackers[] = $rootTracker;
528+
}
529+
}
530+
531+
foreach (array_unique($trackers) as $tracker) {
532+
$urlSearchParams[] = 'tr=' . rawurlencode($tracker);
533+
}
534+
}
535+
536+
return 'magnet:?' . implode('&', $urlSearchParams);
537+
}
538+
489539
/**
490540
* Utility function to clean out keys in the data and info dictionaries that we don't need in
491541
* our torrent file when we go to store it in the DB or serve it up to the user (with the
@@ -667,7 +717,8 @@ public function cleanCache()
667717
}
668718

669719
// Wrapper end function to avoid change the internal pointer of $path,
670-
private static function arrayEnd($array) {
720+
private static function arrayEnd($array)
721+
{
671722
return end($array);
672723
}
673724
}

tests/traits/TorrentFileCommonTrait.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,57 @@ public function testPrivate()
202202
$this->assertFalse($this->torrent->isPrivate());
203203
}
204204

205+
public function testGetMagnetLink()
206+
{
207+
$xtComponent = '';
208+
if ($this->protocol === TorrentFile::PROTOCOL_V1) {
209+
$xtComponent = 'xt=urn:btih:' . $this->infoHashs[TorrentFile::PROTOCOL_V1];
210+
}
211+
212+
if ($this->protocol === TorrentFile::PROTOCOL_V2) {
213+
$xtComponent = 'xt=url:btmh:1220' . $this->infoHashs[TorrentFile::PROTOCOL_V2];
214+
}
215+
216+
if ($this->protocol === TorrentFile::PROTOCOL_HYBRID) {
217+
$xtComponent = 'xt=urn:btih:' . $this->infoHashs[TorrentFile::PROTOCOL_V1] .
218+
'&xt=url:btmh:1220' . $this->infoHashs[TorrentFile::PROTOCOL_V2];
219+
}
220+
221+
$name = $this->fileMode === TorrentFile::FILEMODE_MULTI ? 'tname' : 'file1.dat';
222+
$dnComponent = 'dn=' . rawurlencode($name);
223+
224+
$trComponent = 'tr=https%3A%2F%2Fexample.com%2Fannounce&tr=https%3A%2F%2Fexample1.com%2Fannounce';
225+
226+
$this->assertEquals('magnet:?' . implode('&', [$xtComponent, $dnComponent, $trComponent]), $this->torrent->getMagnetLink());
227+
$this->assertEquals('magnet:?' . implode('&', [$xtComponent, $dnComponent]), $this->torrent->getMagnetLink(true, false));
228+
$this->assertEquals('magnet:?' . implode('&', [$xtComponent, $trComponent]), $this->torrent->getMagnetLink(false, true));
229+
$this->assertEquals('magnet:?' . $xtComponent, $this->torrent->getMagnetLink(false, false));
230+
231+
// torrent without `announce` and `announce-list` should no `&tr=` component in uri.
232+
$torrentWithoutAnnounce = clone $this->torrent;
233+
$torrentWithoutAnnounce->unsetRootField('announce')->unsetRootField('announce-list');
234+
$this->assertEquals('magnet:?' . implode('&', [$xtComponent, $dnComponent]), $torrentWithoutAnnounce->getMagnetLink());
235+
236+
/**
237+
* Torrent with Multitracker Metadata Extension (BEP0012)
238+
* Note:
239+
* 1. if the "announce-list" key is present, the client will ignore the "announce" key
240+
* and only use the URLs in "announce-list". ( From BEP )
241+
* So the "announce" field will not exist in magnet link if "announce-list" exists.
242+
* 2. dupe tracker will be ignored.
243+
*/
244+
$torrentWithEditAnnounce = clone $this->torrent;
245+
$torrentWithEditAnnounce
246+
->setAnnounce('https://127.0.0.1:8888/announce')
247+
->setAnnounceList([
248+
['https://127.0.0.2:8890/announce'],
249+
['https://127.0.0.3:8891/announce', 'https://127.0.0.4:8892/announce'],
250+
['https://127.0.0.2:8890/announce']
251+
]);
252+
$trComponent = 'tr=https%3A%2F%2F127.0.0.2%3A8890%2Fannounce&tr=https%3A%2F%2F127.0.0.3%3A8891%2Fannounce&tr=https%3A%2F%2F127.0.0.4%3A8892%2Fannounce';
253+
$this->assertEquals('magnet:?' . implode('&', [$xtComponent, $dnComponent, $trComponent]), $torrentWithEditAnnounce->getMagnetLink());
254+
}
255+
205256
public function testGetSize()
206257
{
207258
$size = $this->fileMode === TorrentFile::FILEMODE_MULTI ? 33554432 /* 32MiB */ : 16777216 /* 16MiB */;

0 commit comments

Comments
 (0)