Animarr/src/Animarr/Torrent/Torrent.php

307 lines
8.9 KiB
PHP
Executable file

<?php
namespace Animarr\Torrent;
use Animarr\Database;
use Animarr\Downloader;
use Animarr\Request;
class Torrent{
private $announce;
private $creationDate;
private $info;
private $info_hash;
private $files = [];
public static $urls = [];
public static function isTorrentDownloadPath(&$url){
$data = parse_url($url);
if(isset($data["scheme"]) and $data["scheme"] == "magnet"){
return true;
}
if(isset($data["path"]) and substr($data["path"], -8) === ".torrent"){
return true;
}
if(isset($data["path"]) and ($data["host"] == "anidex.info" or $data["host"] == "anidex.moe") and preg_match("#^/dl/.*$#", $data["path"]) > 0){
return true;
}
if(isset($data["path"]) and ($data["host"] == "anirena.com" or $data["host"] == "www.anirena.com") and preg_match("#^/dl/[0-9]+$#", $data["path"]) > 0){
return true;
}
if(isset($data["path"]) and ($data["host"] == "www.frozen-layer.com" or $data["host"] == "frozen-layer.com") and preg_match("#^/descargas/([0-9]+.*)$#", $data["path"], $matches) > 0){
$url = "https://www.frozen-layer.com/descargas/".$matches[1] .".torrent";
return true;
}
if(isset($data["path"]) and ($data["host"] == "anidex.info" or $data["host"] == "anidex.moe") and preg_match("#page=torrent&id=([0-9]+)$#", $data["path"], $matches) > 0){
$url = "https://anidex.info/dl/".$matches[1];
return true;
}
if(isset($data["path"]) and ($data["host"] == "anidex.info" or $data["host"] == "anidex.moe") and preg_match("#/torrent/([0-9]+)$#", $data["path"], $matches) > 0){
$url = "https://anidex.info/dl/".$matches[1];
return true;
}
//if(isset($data["path"]) and ($data["host"] == "nyaa.se" or $data["host"] == "www.nyaa.se") and preg_match("#page=download&tid=/.*$#", $data["path"]) > 0){
//return true;
//}
if(isset($data["path"]) and ($data["host"] == "nyaa.si" or $data["host"] == "www.nyaa.si") and preg_match("#view/[0-9]+/torrent$#", $data["path"]) > 0){
return true;
}
if(isset($data["path"]) and $data["host"] == "animebytes.tv" and preg_match("#torrent/[0-9]+/download#", $data["path"]) > 0){
return true;
}
if(isset($data["path"]) and ($data["host"] == "nyaa.se" or $data["host"] == "nyaa.eu" or $data["host"] == "www.nyaa.se") and preg_match("#page=(view|download)&tid=/.*$#", $data["path"], $matches) > 0){
$url = "https://nyaa.si/view/".$matches[1]."/torrent";
return true;
}
if(isset($data["path"]) and ($data["host"] == "nyaa.si" or $data["host"] == "www.nyaa.si") and preg_match("#view/([0-9]+)(|/torrent|\\.torrent)$#", $data["path"], $matches) > 0){
$url = "https://nyaa.si/view/".$matches[1]."/torrent";
return true;
}
return false;
}
public static function isPrivateTorrent($url){
$data = parse_url($url);
if(!isset($data["host"])){
return false;
}
$domains = Database::getConfigKey("tracker.domain.private", []);
return in_array($data["host"], $domains);
}
public static function getMagnetProperties($magnetUrl){
$data = parse_url($magnetUrl);
if(!isset($data["query"]) or $data["scheme"] != "magnet"){
return [];
}
parse_str($data["query"], $query);
return $query;
}
public static function getMagnetHash($magnetUrl){
$props = self::getMagnetProperties($magnetUrl);
if(isset($props["xt"]) and preg_match('/urn:btih:[ ]*([A-Za-z0-9]{32,40})/', $props["xt"], $matches) > 0){
$hash = $matches[1];
if(strlen($hash) == 32){
$hash = bin2hex(Base32::decode($hash));
}
$hash = strtoupper($hash);
return $hash;
}
return null;
}
public static function queueMagnetTorrent($magnet, $cachePath = null){
if(!Database::getConfigKey("magnet.service.enable", false) or Database::getConfigKey("magnet.service.folder", "") === ""){
return false;
}
$hash = self::getMagnetHash($magnet);
if($hash !== null and $cachePath !== null and file_exists($cachePath . "/". $hash . ".torrent")){
return true;
}
if($hash === null){
Downloader::log("Failed to magnet ".$hash." to MagnetService ($magnet)", "TORRENT");
return false;
}
file_put_contents(Database::getConfigKey("magnet.service.folder", "") . "/" . $hash . ".magnet", $magnet);
#Downloader::log("Added magnet ".$hash." to MagnetService", "TORRENT");
return true;
}
public static function getTorrent($url, $cachePath = null){
$contents = null;
if(strpos($url, "urn:btih:") !== false){
$hash = self::getMagnetHash($url);
if($hash === null){
return "";
}
$path = $cachePath . "/". $hash . ".torrent";
if($cachePath !== null and file_exists($path)){
$contents = file_get_contents($path);
}else{
self::queueMagnetTorrent($url, $cachePath);
return null;
}
}else{
$path = $cachePath . "/url_" . md5($url) . ".torrent";
if($cachePath !== null and file_exists($path)){
$contents = file_get_contents($path);
}
if(($contents === "" and !Database::getConfigKey("torrent.cache.addEmpty", false)) or $contents === null){
$contents = Request::getURL($url);
if(self::isPrivateTorrent($url)){
sleep(Database::getConfigKey("tracker.private.fetchDelay", 5));
}
if($cachePath !== null and strlen($contents) > 40){
file_put_contents($path, $contents);
}elseif($cachePath !== null and Database::getConfigKey("torrent.cache.addEmpty", false)){
file_put_contents($path, "");
}
}
}
return $contents;
}
public static function fromURL($url, $cachePath = null){
if(isset(self::$urls[$url])){
return self::$urls[$url];
}
$contents = self::getTorrent($url, $cachePath);
if($contents === null){
return null;
}
$t = new Torrent($contents);
$hash = $t->getInfoHash();
if($contents != "" and $hash != null and $cachePath !== null and !file_exists($cachePath . "/" . $hash . ".torrent")){
file_put_contents($cachePath . "/" . $hash . ".torrent", $contents);
}
if($contents == ""){
return $t;
}
return self::$urls[$url] = $t;
}
public function __construct($contents){
$c = self::bdecode($contents);
if(!is_array($c)){
return;
}
$this->announce = isset($c["announce-list"]) ? $c["announce-list"] : isset($c["announce"]) ? [[$c["announce"]]] : [];
$this->creationDate = isset($c["creation-date"]) ? $c["creation-date"] : time();
if(isset($c["info"]["files"])){
foreach($c["info"]["files"] as $file){
//Damn padding files
if(strpos($file["path"][0], "_____padding_file") === 0){
continue;
}
array_unshift($file["path"], $c["info"]["name"]);
$this->files[] = [
"size" => $file["length"],
"path" => $file["path"],
"stringPath" => implode("/", $file["path"]),
];
}
}else{
$this->files[] = [
"size" => $c["info"]["length"],
"path" => [$c["info"]["name"]],
"stringPath" => $c["info"]["name"],
];
}
$this->info = $c["info"];
$this->info_hash = $c["info_hash"];
}
public function getCreationDate(){
return $this->creationDate;
}
public function getFiles(){
return $this->files;
}
public function getInfoHash(){
return $this->info_hash;
}
private static function bdecode($s, &$pos = 0, $depth = 0) {
if($pos>=strlen($s)) {
return null;
}
switch($s[$pos]){
case 'd':
$pos++;
$retval=array();
while (isset($s[$pos]) and $s[$pos]!='e'){
$key=self::bdecode($s, $pos, $depth + 1);
$origPos = $pos;
$val=self::bdecode($s, $pos, $depth + 1);
if ($key===null || $val===null)
break;
$retval[$key]=$val;
if($key === "info" and $depth <= 1){
$retval["info_hash"] = strtoupper(sha1(substr($s, $origPos, $pos - $origPos)));
}
}
$retval["isDct"]=true;
$pos++;
return $retval;
case 'l':
$pos++;
$retval=array();
while ($s[$pos]!='e'){
$val=self::bdecode($s, $pos, $depth + 1);
if ($val===null)
break;
$retval[]=$val;
}
$pos++;
return $retval;
case 'i':
$pos++;
$digits=strpos($s, 'e', $pos)-$pos;
$val=round((float)substr($s, $pos, $digits));
$pos+=$digits+1;
return $val;
// case "0": case "1": case "2": case "3": case "4":
// case "5": case "6": case "7": case "8": case "9":
default:
$digits=strpos($s, ':', $pos)-$pos;
if ($digits<0 || $digits >20)
return null;
$len=(int)substr($s, $pos, $digits);
$pos+=$digits+1;
$str=substr($s, $pos, $len);
$pos+=$len;
//echo "pos: $pos str: [$str] len: $len digits: $digits\n";
return (string)$str;
}
return null;
}
}