diff --git a/data/config.php b/data/config.php index b8cdbec..db0f01c 100644 --- a/data/config.php +++ b/data/config.php @@ -142,6 +142,7 @@ class config{ const PROXY_MARGINALIA = false; const PROXY_MOJEEK = false; const PROXY_SC = false; // soundcloud + const PROXY_SWISSCOWS = false; const PROXY_SPOTIFY = false; const PROXY_SOLOFIELD = false; const PROXY_WIBY = false; diff --git a/lib/frontend.php b/lib/frontend.php index 04b08b6..e38d5dc 100644 --- a/lib/frontend.php +++ b/lib/frontend.php @@ -446,407 +446,419 @@ class frontend{ Add favicon */ $host = parse_url($link); - $esc = - explode( - ".", - $host["host"], - 2 - ); - if( - count($esc) === 2 && - $esc[0] == "www" - ){ + // special case for when we're not drawing a full url + if(!isset($host["host"])){ - $esc = $esc[1]; + $payload = + '
' . + ''; }else{ - $esc = $esc[0]; - } - - $esc = substr($esc, 0, 2); - - $urlencode = urlencode($link); - - $payload = - '
' . - '' . - '
'; - - /* - Add archive links - */ - if( - $host["host"] == "boards.4chan.org" || - $host["host"] == "boards.4channel.org" - ){ + $esc = + explode( + ".", + $host["host"], + 2 + ); - $archives = []; - $path = explode("/", $host["path"]); - $count = count($path); - // /pol/thread/417568063/post-shitty-memes-if-you-want-to + if( + count($esc) === 2 && + $esc[0] == "www" + ){ + + $esc = $esc[1]; + }else{ + + $esc = $esc[0]; + } - if($count !== 0){ + $esc = substr($esc, 0, 2); + + $urlencode = urlencode($link); + + $payload = + '
' . + '' . + '
'; + + /* + Add archive links + */ + if( + $host["host"] == "boards.4chan.org" || + $host["host"] == "boards.4channel.org" + ){ - $isboard = true; + $archives = []; + $path = explode("/", $host["path"]); + $count = count($path); + // /pol/thread/417568063/post-shitty-memes-if-you-want-to - switch($path[1]){ + if($count !== 0){ - case "con": - break; + $isboard = true; - case "q": - $archives[] = "desuarchive.org"; - break; + switch($path[1]){ - case "qa": - $archives[] = "desuarchive.org"; - break; + case "con": + break; - case "qb": - $archives[] = "arch.b4k.co"; - break; + case "q": + $archives[] = "desuarchive.org"; + break; + + case "qa": + $archives[] = "desuarchive.org"; + break; + + case "qb": + $archives[] = "arch.b4k.co"; + break; + + case "trash": + $archives[] = "desuarchive.org"; + break; - case "trash": - $archives[] = "desuarchive.org"; - break; - - case "a": - $archives[] = "desuarchive.org"; - break; - - case "c": - $archives[] = "desuarchive.org"; - break; - - case "w": - break; - - case "m": - $archives[] = "desuarchive.org"; - break; - - case "cgl": - $archives[] = "desuarchive.org"; - $archives[] = "warosu.org"; - break; - - case "f": - $archives[] = "archive.4plebs.org"; - break; - - case "n": - break; - - case "jp": - $archives[] = "warosu.org"; - break; - - case "vt": - $archives[] = "warosu.org"; - break; - - case "v": - $archives[] = "arch.b4k.co"; - break; - - case "vg": - $archives[] = "arch.b4k.co"; - break; - - case "vm": - $archives[] = "arch.b4k.co"; - break; - - case "vmg": - $archives[] = "arch.b4k.co"; - break; - - case "vp": - $archives[] = "arch.b4k.co"; - break; - - case "vr": - $archives[] = "desuarchive.org"; - $archives[] = "warosu.org"; - break; - - case "vrpg": - $archives[] = "arch.b4k.co"; - break; - - case "vst": - $archives[] = "arch.b4k.co"; - break; - - case "co": - $archives[] = "desuarchive.org"; - break; - - case "g": - $archives[] = "desuarchive.org"; - $archives[] = "arch.b4k.co"; - break; - - case "tv": - $archives[] = "archive.4plebs.org"; - break; - - case "k": - $archives[] = "desuarchive.org"; - break; - - case "o": - $archives[] = "archive.4plebs.org"; - break; - - case "an": - $archives[] = "desuarchive.org"; - break; - - case "tg": - $archives[] = "desuarchive.org"; - $archives[] = "archive.4plebs.org"; - break; - - case "sp": - $archives[] = "archive.4plebs.org"; - break; - - case "xs": - $archives[] = "eientei.xyz"; - break; - - case "pw": - break; - - case "sci": - $archives[] = "warosu.org"; - $archives[] = "eientei.xyz"; - break; - - case "his": - $archives[] = "desuarchive.org"; - break; - - case "int": - $archives[] = "desuarchive.org"; - break; - - case "out": - break; - - case "toy": - break; - - case "i": - $archives[] = "archiveofsins.com"; - $archives[] = "eientei.xyz"; - break; - - case "po": - break; - - case "p": - break; - - case "ck": - $archives[] = "warosu.org"; - break; - - case "ic": - $archives[] = "warosu.org"; - break; - - case "wg": - break; - - case "lit": - $archives[] = "warosu.org"; - break; - - case "mu": - $archives[] = "desuarchive.org"; - break; - - case "fa": - $archives[] = "warosu.org"; - break; - - case "3": - $archives[] = "warosu.org"; - $archives[] = "eientei.xyz"; - break; - - case "gd": - break; - - case "diy": - $archives[] = "warosu.org"; - break; - - case "wsg": - $archives[] = "desuarchive.org"; - break; - - case "qst": - break; - - case "biz": - $archives[] = "warosu.org"; - break; - - case "trv": - $archives[] = "archive.4plebs.org"; - break; - - case "fit": - $archives[] = "desuarchive.org"; - break; - - case "x": - $archives[] = "archive.4plebs.org"; - break; - - case "adv": - $archives[] = "archive.4plebs.org"; - break; - - case "lgbt": - $archives[] = "archiveofsins.com"; - break; - - case "mlp": - $archives[] = "desuarchive.org"; - $archives[] = "arch.b4k.co"; - break; - - case "news": - break; - - case "wsr": - break; - - case "vip": - break; - - case "b": - $archives[] = "thebarchive.com"; - break; - - case "r9k": - $archives[] = "desuarchive.org"; - break; - - case "pol": - $archives[] = "archive.4plebs.org"; - break; - - case "bant": - $archives[] = "thebarchive.com"; - break; - - case "soc": - $archives[] = "archiveofsins.com"; - break; - - case "s4s": - $archives[] = "archive.4plebs.org"; - break; - - case "s": - $archives[] = "archiveofsins.com"; - break; - - case "hc": - $archives[] = "archiveofsins.com"; - break; - - case "hm": - $archives[] = "archiveofsins.com"; - break; - - case "h": - $archives[] = "archiveofsins.com"; - break; - - case "e": - break; - - case "u": - $archives[] = "archiveofsins.com"; - break; - - case "d": - $archives[] = "desuarchive.org"; - break; - - case "t": - $archives[] = "archiveofsins.com"; - break; - - case "hr": - $archives[] = "archive.4plebs.org"; - break; - - case "gif": - break; - - case "aco": - $archives[] = "desuarchive.org"; - break; - - case "r": - $archives[] = "archiveofsins.com"; - break; - - default: - $isboard = false; - break; - } - - if($isboard === true){ - - $archives[] = "archived.moe"; - } - - $trail = ""; - - if( - isset($path[2]) && - isset($path[3]) && - $path[2] == "thread" - ){ - - $trail .= "/" . $path[1] . "/thread/" . $path[3]; - }elseif($isboard){ - - $trail = "/" . $path[1] . "/"; - } - - for($i=0; $i' . - '' . $archives[$i][0] . $archives[$i][1] . '' . - $archives[$i] . - ''; + case "a": + $archives[] = "desuarchive.org"; + break; + + case "c": + $archives[] = "desuarchive.org"; + break; + + case "w": + break; + + case "m": + $archives[] = "desuarchive.org"; + break; + + case "cgl": + $archives[] = "desuarchive.org"; + $archives[] = "warosu.org"; + break; + + case "f": + $archives[] = "archive.4plebs.org"; + break; + + case "n": + break; + + case "jp": + $archives[] = "warosu.org"; + break; + + case "vt": + $archives[] = "warosu.org"; + break; + + case "v": + $archives[] = "arch.b4k.co"; + break; + + case "vg": + $archives[] = "arch.b4k.co"; + break; + + case "vm": + $archives[] = "arch.b4k.co"; + break; + + case "vmg": + $archives[] = "arch.b4k.co"; + break; + + case "vp": + $archives[] = "arch.b4k.co"; + break; + + case "vr": + $archives[] = "desuarchive.org"; + $archives[] = "warosu.org"; + break; + + case "vrpg": + $archives[] = "arch.b4k.co"; + break; + + case "vst": + $archives[] = "arch.b4k.co"; + break; + + case "co": + $archives[] = "desuarchive.org"; + break; + + case "g": + $archives[] = "desuarchive.org"; + $archives[] = "arch.b4k.co"; + break; + + case "tv": + $archives[] = "archive.4plebs.org"; + break; + + case "k": + $archives[] = "desuarchive.org"; + break; + + case "o": + $archives[] = "archive.4plebs.org"; + break; + + case "an": + $archives[] = "desuarchive.org"; + break; + + case "tg": + $archives[] = "desuarchive.org"; + $archives[] = "archive.4plebs.org"; + break; + + case "sp": + $archives[] = "archive.4plebs.org"; + break; + + case "xs": + $archives[] = "eientei.xyz"; + break; + + case "pw": + break; + + case "sci": + $archives[] = "warosu.org"; + $archives[] = "eientei.xyz"; + break; + + case "his": + $archives[] = "desuarchive.org"; + break; + + case "int": + $archives[] = "desuarchive.org"; + break; + + case "out": + break; + + case "toy": + break; + + case "i": + $archives[] = "archiveofsins.com"; + $archives[] = "eientei.xyz"; + break; + + case "po": + break; + + case "p": + break; + + case "ck": + $archives[] = "warosu.org"; + break; + + case "ic": + $archives[] = "warosu.org"; + break; + + case "wg": + break; + + case "lit": + $archives[] = "warosu.org"; + break; + + case "mu": + $archives[] = "desuarchive.org"; + break; + + case "fa": + $archives[] = "warosu.org"; + break; + + case "3": + $archives[] = "warosu.org"; + $archives[] = "eientei.xyz"; + break; + + case "gd": + break; + + case "diy": + $archives[] = "warosu.org"; + break; + + case "wsg": + $archives[] = "desuarchive.org"; + break; + + case "qst": + break; + + case "biz": + $archives[] = "warosu.org"; + break; + + case "trv": + $archives[] = "archive.4plebs.org"; + break; + + case "fit": + $archives[] = "desuarchive.org"; + break; + + case "x": + $archives[] = "archive.4plebs.org"; + break; + + case "adv": + $archives[] = "archive.4plebs.org"; + break; + + case "lgbt": + $archives[] = "archiveofsins.com"; + break; + + case "mlp": + $archives[] = "desuarchive.org"; + $archives[] = "arch.b4k.co"; + break; + + case "news": + break; + + case "wsr": + break; + + case "vip": + break; + + case "b": + $archives[] = "thebarchive.com"; + break; + + case "r9k": + $archives[] = "desuarchive.org"; + break; + + case "pol": + $archives[] = "archive.4plebs.org"; + break; + + case "bant": + $archives[] = "thebarchive.com"; + break; + + case "soc": + $archives[] = "archiveofsins.com"; + break; + + case "s4s": + $archives[] = "archive.4plebs.org"; + break; + + case "s": + $archives[] = "archiveofsins.com"; + break; + + case "hc": + $archives[] = "archiveofsins.com"; + break; + + case "hm": + $archives[] = "archiveofsins.com"; + break; + + case "h": + $archives[] = "archiveofsins.com"; + break; + + case "e": + break; + + case "u": + $archives[] = "archiveofsins.com"; + break; + + case "d": + $archives[] = "desuarchive.org"; + break; + + case "t": + $archives[] = "archiveofsins.com"; + break; + + case "hr": + $archives[] = "archive.4plebs.org"; + break; + + case "gif": + break; + + case "aco": + $archives[] = "desuarchive.org"; + break; + + case "r": + $archives[] = "archiveofsins.com"; + break; + + default: + $isboard = false; + break; + } + + if($isboard === true){ + + $archives[] = "archived.moe"; + } + + $trail = ""; + + if( + isset($path[2]) && + isset($path[3]) && + $path[2] == "thread" + ){ + + $trail .= "/" . $path[1] . "/thread/" . $path[3]; + }elseif($isboard){ + + $trail = "/" . $path[1] . "/"; + } + + for($i=0; $i' . + '' . $archives[$i][0] . $archives[$i][1] . '' . + $archives[$i] . + ''; + } } } + + $payload .= + 'arArchive.org' . + 'arArchive.is' . + 'ghGhostarchive' . + 'arArquivo.pt' . + 'biBing cache' . + 'meMegalodon' . + '
'; } - $payload .= - 'arArchive.org' . - 'arArchive.is' . - 'ghGhostarchive' . - 'arArquivo.pt' . - 'biBing cache' . - 'meMegalodon' . - '
'; - /* Draw link */ @@ -862,10 +874,12 @@ class frontend{ } // merge https://site together - $parts = [ - $parts[0] . $parts[1] . '//' . $parts[2], - ...array_slice($parts, 3, count($parts) - 1) - ]; + if(isset($host["host"])){ + $parts = [ + $parts[0] . $parts[1] . '//' . $parts[2], + ...array_slice($parts, 3, count($parts) - 1) + ]; + } $c = count($parts); for($i=0; $i<$c; $i++){ @@ -1034,7 +1048,8 @@ class frontend{ $filters["scraper"] = [ "display" => "Scraper", "option" => [ - "sc" => "SoundCloud" + "sc" => "SoundCloud", + "swisscows" => "Swisscows (SoundCloud)" //"spotify" => "Spotify" ] ]; diff --git a/resolver.php b/resolver.php new file mode 100644 index 0000000..cf7173d --- /dev/null +++ b/resolver.php @@ -0,0 +1,66 @@ +do400("Missing or invalid scraper"); + return; + } + + if( + !isset($_GET["target"]) || + !is_string($_GET["target"]) + ){ + + $this->do400("Missing or invalid target"); + return; + } + + $scraper = $_GET["scraper"]; + $target = $_GET["target"]; + + try{ + + include "resolver/{$scraper}.php"; + $resolver = new $scraper(); + $link = $resolver->resolve($target); + + if(is_string($link)){ + + header("Location: {$link}"); + } + }catch(Exception $error){ + + $this->do404("Fuck! Failed to resolve URL: " . $error->getMessage()); + } + } + + public function do400($message){ + + header("Content-Type: text/plain"); + http_response_code(400); + echo $message; + } + + public function do404($message){ + + header("Content-Type: text/plain"); + http_response_code(404); + echo $message; + } +} diff --git a/resolver/sc.php b/resolver/sc.php new file mode 100644 index 0000000..3606274 --- /dev/null +++ b/resolver/sc.php @@ -0,0 +1,117 @@ +backend = new backend("sc"); + + include "lib/fuckhtml.php"; + $this->fuckhtml = new fuckhtml(); + } + + private function get($proxy, $url, $get = []){ + + $curlproc = curl_init(); + + if($get !== []){ + $get = http_build_query($get); + $url .= "?" . $get; + } + + curl_setopt($curlproc, CURLOPT_URL, $url); + + // http2 bypass + curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + + $headers = + ["User-Agent: " . config::USER_AGENT, + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language: en-US,en;q=0.5", + "Accept-Encoding: gzip", + "DNT: 1", + "Sec-GPC: 1", + "Connection: keep-alive", + "Upgrade-Insecure-Requests: 1", + "Sec-Fetch-Dest: document", + "Sec-Fetch-Mode: navigate", + "Sec-Fetch-Site: none", + "Sec-Fetch-User: ?1", + "Priority: u=0, i"]; + + $this->backend->assign_proxy($curlproc, $proxy); + + curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding + curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers); + + curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30); + curl_setopt($curlproc, CURLOPT_TIMEOUT, 30); + + $data = curl_exec($curlproc); + + if(curl_errno($curlproc)){ + throw new Exception(curl_error($curlproc)); + } + + curl_close($curlproc); + return $data; + } + + function resolve($id){ + + if( + !preg_match( + '/^(t|p)[0-9]+$/', + $id + ) + ){ + + throw new Exception("ID is invalid"); + } + + $type = $id[0] == "t" ? "track" : "playlist"; + $id = substr($id, 1); + + try{ + $html = + $this->get( + $this->backend->get_ip(), + "https://w.soundcloud.com/player/", + [ + "url" => "http://api.soundcloud.com/{$type}s/{$id}", + "show_artwork" => "true" + ] + ); + }catch(Exception $error){ + + throw new Exception("Failed to fetch song embed page"); + } + + $this->fuckhtml->load($html); + + $link = + $this->fuckhtml + ->getElementsByAttributeValue( + "rel", + "canonical", + "link" + ); + + if(count($link) === 0){ + + throw new Exception("Soundcloud could not resolve the song ID to an URL"); + } + + return + $this->fuckhtml + ->getTextContent( + $link[0] + ["attributes"] + ["href"] + ); + } +} diff --git a/scraper/sc.php b/scraper/sc.php index 1c6132e..3475c9a 100644 --- a/scraper/sc.php +++ b/scraper/sc.php @@ -397,7 +397,12 @@ class sc{ break; case "track": - if(stripos($item["monetization_model"], "TIER") === false){ + $stream = [ + "endpoint" => null, + "url" => null + ]; + + /*if(stripos($item["monetization_model"], "TIER") === false){ $stream = [ "endpoint" => "sc", @@ -413,7 +418,7 @@ class sc{ "endpoint" => null, "url" => null ]; - } + }*/ // parse track $out["song"][] = [ diff --git a/scraper/swisscows.php b/scraper/swisscows.php new file mode 100644 index 0000000..0d9dc65 --- /dev/null +++ b/scraper/swisscows.php @@ -0,0 +1,252 @@ +backend = new backend("swisscows"); + + include "lib/fuckhtml.php"; + $this->fuckhtml = new fuckhtml(); + } + + public function getfilters($page){ + + return [ + "type" => [ + "display" => "Type", + "option" => [ + "track" => "Tracks", + "playlist" => "Playlists" + ] + ] + ]; + } + + private function get($proxy, $url, $get = [], $web_req = false){ + + $curlproc = curl_init(); + + if($get !== []){ + $get = http_build_query($get); + $url .= "?" . $get; + } + + curl_setopt($curlproc, CURLOPT_URL, $url); + + curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding + + // use http2 + curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + + curl_setopt($curlproc, CURLOPT_HTTPHEADER, + ["User-Agent: " . config::USER_AGENT, + "Accept: */*", + "Accept-Language: en-US,en;q=0.5", + "Accept-Encoding: gzip, deflate, br, zstd", + "Access-Control-Request-Method: GET", + "Access-Control-Request-Headers: cache-control", + "Referer: https://swisscows.com/", + "Origin: https://swisscows.com", + "DNT: 1", + "Sec-GPC: 1", + "Connection: keep-alive", + "Sec-Fetch-Dest: empty", + "Sec-Fetch-Mode: cors", + "Sec-Fetch-Site: same-site", + "Priority: u=4", + "TE: trailers"] + ); + + curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30); + curl_setopt($curlproc, CURLOPT_TIMEOUT, 30); + + $this->backend->assign_proxy($curlproc, $proxy); + + $data = curl_exec($curlproc); + + if(curl_errno($curlproc)){ + + throw new Exception(curl_error($curlproc)); + } + + curl_close($curlproc); + return $data; + } + + public function music($get, $last_attempt = false){ + + if($get["npt"]){ + + [$params, $proxy] = $this->backend->get($get["npt"], "music"); + $params = json_decode($params, true); + $type = $params["type"]; + $search = $params["s"]; + $offset = $params["offset"]; + + }else{ + + $search = $get["s"]; + if(strlen($search) === 0){ + + throw new Exception("Search term is empty!"); + } + + $type = $get["type"]; + $offset = 0; + $proxy = $this->backend->get_ip(); + } + + try{ + + $json = + $this->get( + $proxy, + "https://api.swisscows.com/audio/search/{$type}s", + [ + "query" => $search, + "offset" => $offset, + "itemsCount" => 20, + "region" => "en-US" + ] + ); + + }catch(Exception $error){ + + throw new Exception("Failed to fetch JSON"); + } + + $json = json_decode($json, true); + + if($json === null){ + + throw new Exception("Failed to decode JSON"); + } + + $out = [ + "status" => "ok", + "npt" => null, + "song" => [], + "playlist" => [], + "album" => [], + "podcast" => [], + "author" => [], + "user" => [] + ]; + + if(!isset($json["items"])){ + + throw new Exception("Swisscows did not return an items object"); + } + + if($type == "track"){ + foreach($json["items"] as $item){ + + $tags = $item["tags"]; + if(!empty($item["genre"])){ + + $tags[] = $item["genre"]; + } + + $out["song"][] = [ + "title" => $item["title"], + "description" => implode(", ", $tags), + "url" => "/resolver?scraper=sc&target=t{$item["id"]}", + "views" => null, + "author" => [ + "name" => null, + "url" => null, + "avatar" => null + ], + "thumb" => [ + "ratio" => "1:1", + "url" => $item["artworkUrl"] + ], + "date" => null, + "duration" => $this->convert_time($item["duration"]), + "stream" => [ + "endpoint" => null, + "url" => null + ] + ]; + } + }else{ + + foreach($json["items"] as $item){ + + $out["playlist"][] = [ + "title" => $item["title"], + "description" => $this->limitstrlen($item["description"]), + "author" => [ + "name" => null, + "url" => null, + "avatar" => null + ], + "thumb" => [ + "ratio" => "1:1", + "url" => $item["artworkUrl"] + ], + "date" => null, + "duration" => $this->convert_time($item["duration"]), + "url" => "/resolver?scraper=sc&target=p{$item["id"]}", + ]; + } + } + + // + // get NPT + // + if( + isset($json["nextOffset"]) && + $json["nextOffset"] !== null + ){ + + $out["npt"] = + $this->backend->store( + json_encode( + [ + "type" => $type, + "s" => $search, + "offset" => $json["nextOffset"] + ] + ), + "music", + $proxy + ); + } + + return $out; + } + + private function limitstrlen($text){ + + return + explode( + "\n", + wordwrap( + str_replace( + ["\n\r", "\r\n", "\n", "\r"], + " ", + $text + ), + 300, + "\n" + ), + 2 + )[0]; + } + + private function convert_time($time){ + + list($hours, $minutes, $seconds) = explode(':', $time); + + return + ((int)$hours * 3600) + + ((int)$minutes * 60) + + (float)$seconds; + } +} diff --git a/settings.php b/settings.php index afb1a36..d78a210 100644 --- a/settings.php +++ b/settings.php @@ -388,11 +388,15 @@ $settings = [ [ "value" => "sc", "text" => "SoundCloud" - ]//, - //[ - // "value" => "spotify", - // "text" => "Spotify" - //] + ], + [ + "value" => "swisscows", + "text" => "Swisscows" + ] + /*[ + "value" => "spotify", + "text" => "Spotify" + ]*/ ] ] ]