forked from Korbs/4get
soundcloud lolllllll
This commit is contained in:
parent
cfd44438ae
commit
c8ab934b10
13 changed files with 982 additions and 18 deletions
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
lib/test.html
|
||||
lib/postdata.json
|
||||
lib/nextpage.json
|
||||
scraper/brave.html
|
||||
scraper/yandex.json
|
||||
scraper/marginalia.json
|
||||
banner_og/
|
||||
scraper/mojeek.html
|
||||
scraper/google.html
|
||||
scraper/google-img.html
|
||||
scraper/google-video.html
|
||||
scraper/google-news.html
|
||||
scraper/google-img-nextpage.html
|
||||
scraper/brave-image.html
|
||||
scraper/brave-video.html
|
||||
scraper/facebook.html
|
||||
scraper/facebook-nextpage.json
|
||||
scraper/yandex-video.json
|
||||
scraper/yandex.html
|
||||
scraper/soundcloud.json
|
||||
scraper/mp3-pm.html
|
||||
banner/*
|
||||
!banner/*default*
|
29
api.txt
29
api.txt
|
@ -242,6 +242,21 @@
|
|||
the endpoint above.
|
||||
|
||||
|
||||
+ /api/v1/music
|
||||
Each entry under "song" contains a array index called "stream" that
|
||||
looks like this ::
|
||||
|
||||
endpoint: audio_sc
|
||||
url: https://api-v2.soundcloud <...>
|
||||
|
||||
|
||||
When the endpoint is "audio_sc", you MUST use 4get's audio_sc
|
||||
endpoint, for example, if you want an audio stream back. Otherwise,
|
||||
you are free to handle the json+m3u8 crap yourself. If the endpoint
|
||||
is equal to "audio", that URL SHOULD return a valid HTTP audio
|
||||
stream, and using the "audio" endpoint becomes optional again.
|
||||
|
||||
|
||||
+ /favicon
|
||||
Get the favicon for a website. The only parameter is "s", and must
|
||||
include the protocol.
|
||||
|
@ -284,6 +299,20 @@
|
|||
The parameter is "s" for the audio link.
|
||||
|
||||
|
||||
+ /audio_sc
|
||||
Get a proxied audio file for SoundCloud. Does not support downloads
|
||||
trough WGET or CURL, since it returns 30kb~160kb "206 Partial
|
||||
Content" parts, due to technical limitations that comes with
|
||||
converting m3u8 playlists to seekable audio files. If you use this
|
||||
endpoint, you must support these 206 codes and also handle the
|
||||
initial 302 HTTP redirect. I used this method as I didn't want to
|
||||
store information about your request needlessly. This method also
|
||||
allows noJS users to access the files.
|
||||
|
||||
The parameter is "s" for the SoundCloud JSON m3u8 abomination. It
|
||||
does not support "normal" SoundCloud URLs at this time.
|
||||
|
||||
|
||||
+ Appendix
|
||||
If you have any questions or need clarifications, please send an
|
||||
email my way to will at lolcat.ca
|
||||
|
|
26
api/v1/music.php
Normal file
26
api/v1/music.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
header("Content-Type: application/json");
|
||||
|
||||
chdir("../../");
|
||||
|
||||
include "lib/frontend.php";
|
||||
$frontend = new frontend();
|
||||
|
||||
[$scraper, $filters] = $frontend->getscraperfilters(
|
||||
"music",
|
||||
isset($_GET["scraper"]) ? $_GET["scraper"] : null
|
||||
);
|
||||
|
||||
$get = $frontend->parsegetfilters($_GET, $filters);
|
||||
|
||||
try{
|
||||
echo json_encode(
|
||||
$scraper->music($get),
|
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
|
||||
);
|
||||
|
||||
}catch(Exception $e){
|
||||
|
||||
echo json_encode(["status" => $e->getMessage()]);
|
||||
}
|
223
audio_sc.php
Normal file
223
audio_sc.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
new sc_audio();
|
||||
|
||||
class sc_audio{
|
||||
|
||||
public function __construct(){
|
||||
|
||||
include "lib/curlproxy.php";
|
||||
$this->proxy = new proxy();
|
||||
|
||||
if(isset($_GET["u"])){
|
||||
|
||||
/*
|
||||
we're now proxying audio
|
||||
*/
|
||||
$viewkey = $_GET["u"];
|
||||
|
||||
if(!isset($_GET["r"])){
|
||||
|
||||
$this->do404("Ranges(r) are missing");
|
||||
}
|
||||
|
||||
$ranges = explode(",", $_GET["r"]);
|
||||
|
||||
// sanitize ranges
|
||||
foreach($ranges as &$range){
|
||||
|
||||
if(!is_numeric($range)){
|
||||
|
||||
$this->do404("Invalid range specified");
|
||||
}
|
||||
|
||||
$range = (int)$range;
|
||||
}
|
||||
|
||||
// sort ranges (just to make sure)
|
||||
sort($ranges);
|
||||
|
||||
// convert ranges to pairs
|
||||
$last = -1;
|
||||
foreach($ranges as &$r){
|
||||
|
||||
$tmp = $r;
|
||||
$r = [$last + 1, $r];
|
||||
|
||||
$last = $tmp;
|
||||
}
|
||||
|
||||
$browser_headers = getallheaders();
|
||||
|
||||
// get the requested range from client
|
||||
$client_range = 0;
|
||||
foreach($browser_headers as $key => $value){
|
||||
|
||||
if(strtolower($key) == "range"){
|
||||
|
||||
preg_match(
|
||||
'/bytes=([0-9]+)/',
|
||||
$value,
|
||||
$client_regex
|
||||
);
|
||||
|
||||
if(isset($client_regex[1])){
|
||||
|
||||
$client_range = (int)$client_regex[1];
|
||||
}else{
|
||||
|
||||
$client_range = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(
|
||||
$client_range < 0 ||
|
||||
$client_range > $ranges[count($ranges) - 1][1]
|
||||
){
|
||||
|
||||
// range is not satisfiable
|
||||
http_response_code(416);
|
||||
header("Content-Type: text/plain");
|
||||
die();
|
||||
}
|
||||
|
||||
$rng = null;
|
||||
for($i=0; $i<count($ranges); $i++){
|
||||
|
||||
if($ranges[$i][0] <= $client_range){
|
||||
|
||||
$rng = $ranges[$i];
|
||||
}
|
||||
}
|
||||
|
||||
// proxy data!
|
||||
http_response_code(206); // partial content
|
||||
header("Accept-Ranges: bytes");
|
||||
header("Content-Range: bytes {$rng[0]}-{$rng[1]}/" . ($ranges[count($ranges) - 1][1] + 1));
|
||||
|
||||
$viewkey =
|
||||
preg_replace(
|
||||
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
|
||||
'/media/$1/' . $rng[0] . '/' . $rng[1],
|
||||
$viewkey
|
||||
);
|
||||
|
||||
try{
|
||||
|
||||
$this->proxy->stream_linear_audio(
|
||||
$viewkey
|
||||
);
|
||||
}catch(Exception $error){
|
||||
|
||||
$this->do404("Could not read stream");
|
||||
}
|
||||
|
||||
die();
|
||||
}
|
||||
|
||||
/*
|
||||
redirect user to correct resource
|
||||
we need to scrape and store the byte positions in the result URL
|
||||
*/
|
||||
if(!isset($_GET["s"])){
|
||||
|
||||
$this->do404("The URL(s) parameter is missing");
|
||||
}
|
||||
|
||||
$viewkey = $_GET["s"];
|
||||
|
||||
if(
|
||||
preg_match(
|
||||
'/soundcloud\.com$/',
|
||||
parse_url($viewkey, PHP_URL_HOST)
|
||||
) === false
|
||||
){
|
||||
|
||||
$this->do404("This endpoint can only be used for soundcloud streams");
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
$json = $this->proxy->get($viewkey)["body"];
|
||||
}catch(Exception $error){
|
||||
|
||||
$this->do404("Curl error: " . $error->getMessage());
|
||||
}
|
||||
|
||||
$json = json_decode($json, true);
|
||||
|
||||
if(!isset($json["url"])){
|
||||
|
||||
$this->do404("Could not get URL from JSON");
|
||||
}
|
||||
|
||||
$viewkey = $json["url"];
|
||||
|
||||
$m3u8 = $this->proxy->get($viewkey)["body"];
|
||||
|
||||
$m3u8 = explode("\n", $m3u8);
|
||||
|
||||
$lineout = null;
|
||||
$streampos_arr = [];
|
||||
foreach($m3u8 as $line){
|
||||
|
||||
$line = trim($line);
|
||||
if($line[0] == "#"){
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if($lineout === null){
|
||||
$lineout = $line;
|
||||
}
|
||||
|
||||
preg_match(
|
||||
'/\/media\/[0-9]+\/([0-9]+)\/([0-9]+)/',
|
||||
$line,
|
||||
$matches
|
||||
);
|
||||
|
||||
if(isset($matches[0])){
|
||||
|
||||
$streampos_arr[] = [
|
||||
(int)$matches[1],
|
||||
(int)$matches[2]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if($lineout === null){
|
||||
|
||||
$this->do404("Could not get stream URL");
|
||||
}
|
||||
|
||||
$lineout =
|
||||
preg_replace(
|
||||
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
|
||||
'/media/$1/0/0',
|
||||
$lineout
|
||||
);
|
||||
|
||||
$streampos = [];
|
||||
|
||||
foreach($streampos_arr as $pos){
|
||||
|
||||
$streampos[] = $pos[1];
|
||||
}
|
||||
|
||||
$streampos = implode(",", $streampos);
|
||||
|
||||
header("Location: audio_sc?u=" . urlencode($lineout) . "&r=$streampos");
|
||||
header("Accept-Ranges: bytes");
|
||||
}
|
||||
|
||||
private function do404($error){
|
||||
|
||||
http_response_code(404);
|
||||
header("Content-Type: text/plain");
|
||||
header("X-Error: $error");
|
||||
die();
|
||||
}
|
||||
}
|
|
@ -150,7 +150,7 @@ class proxy{
|
|||
$curl,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0",
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip, deflate",
|
||||
|
@ -178,7 +178,7 @@ class proxy{
|
|||
$curl,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0",
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
"Accept: image/avif,image/webp,*/*",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip, deflate",
|
||||
|
@ -380,7 +380,7 @@ class proxy{
|
|||
$curl,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0",
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
"Accept: image/avif,image/webp,*/*",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
|
@ -396,7 +396,7 @@ class proxy{
|
|||
$curl,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0",
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
"Accept: audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
|
|
|
@ -98,7 +98,7 @@ class frontend{
|
|||
]);
|
||||
}
|
||||
|
||||
public function drawtextresult($site, $greentext = null, $duration = null, $keywords, $tabindex = true){
|
||||
public function drawtextresult($site, $greentext = null, $duration = null, $keywords, $tabindex = true, $customhtml = null){
|
||||
|
||||
$payload =
|
||||
'<div class="text-result">';
|
||||
|
@ -188,6 +188,8 @@ class frontend{
|
|||
'</div>';
|
||||
}
|
||||
|
||||
$payload .= $customhtml;
|
||||
|
||||
$payload .= '</a>';
|
||||
|
||||
/*
|
||||
|
@ -764,6 +766,7 @@ class frontend{
|
|||
'<a href="https://webcache.googleusercontent.com/search?q=cache:' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://google.com" alt="go">Google cache</a>' .
|
||||
'<a href="https://web.archive.org/web/' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.org" alt="ar">Archive.org</a>' .
|
||||
'<a href="https://archive.is/newest/' . htmlspecialchars($link) . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.is" alt="ar">Archive.is</a>' .
|
||||
'<a href="https://ghostarchive.org/search?term=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://ghostarchive.org" alt="gh">Ghostarchive</a>' .
|
||||
'<a href="https://www.bing.com/search?q=url%3A' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://bing.com" alt="bi">Bing cache</a>' .
|
||||
'<a href="https://megalodon.jp/?url=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://megalodon.jp" alt="me">Megalodon</a>' .
|
||||
'</div>';
|
||||
|
@ -835,6 +838,10 @@ class frontend{
|
|||
case "news":
|
||||
$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null;
|
||||
break;
|
||||
|
||||
case "music":
|
||||
$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null;
|
||||
break;
|
||||
}
|
||||
|
||||
if(
|
||||
|
@ -923,6 +930,14 @@ class frontend{
|
|||
"mojeek" => "Mojeek"
|
||||
]
|
||||
];
|
||||
|
||||
case "music":
|
||||
$filters["scraper"] = [
|
||||
"display" => "Scraper",
|
||||
"option" => [
|
||||
"sc" => "SoundCloud"
|
||||
]
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -994,6 +1009,11 @@ class frontend{
|
|||
include "scraper/wiby.php";
|
||||
$lib = new wiby();
|
||||
break;
|
||||
|
||||
case "sc":
|
||||
include "scraper/sc.php";
|
||||
$lib = new sc();
|
||||
break;
|
||||
}
|
||||
|
||||
// set scraper on $_GET
|
||||
|
@ -1169,7 +1189,7 @@ class frontend{
|
|||
|
||||
$html = null;
|
||||
|
||||
foreach(["web", "images", "videos", "news"] as $type){
|
||||
foreach(["web", "images", "videos", "news", "music"] as $type){
|
||||
|
||||
$html .= '<a href="/' . $type . '?s=' . urlencode($query);
|
||||
|
||||
|
@ -1303,7 +1323,7 @@ class frontend{
|
|||
return htmlspecialchars($image);
|
||||
}
|
||||
|
||||
return "/proxy?i=" . urlencode($image) . "&s=" . $format;
|
||||
return "/proxy.php?i=" . urlencode($image) . "&s=" . $format;
|
||||
}
|
||||
|
||||
public function htmlnextpage($gets, $npt, $page){
|
||||
|
|
224
music.php
Normal file
224
music.php
Normal file
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
Initialize random shit
|
||||
*/
|
||||
include "lib/frontend.php";
|
||||
$frontend = new frontend();
|
||||
|
||||
[$scraper, $filters] = $frontend->getscraperfilters("music");
|
||||
|
||||
$get = $frontend->parsegetfilters($_GET, $filters);
|
||||
|
||||
$frontend->loadheader(
|
||||
$get,
|
||||
$filters,
|
||||
"music"
|
||||
);
|
||||
|
||||
$payload = [
|
||||
"class" => "",
|
||||
"right-left" => "",
|
||||
"right-right" => "",
|
||||
"left" => ""
|
||||
];
|
||||
|
||||
try{
|
||||
$results = $scraper->music($get);
|
||||
|
||||
}catch(Exception $error){
|
||||
|
||||
echo
|
||||
$frontend->drawerror(
|
||||
"Shit",
|
||||
'This scraper returned an error:' .
|
||||
'<div class="code">' . htmlspecialchars($error->getMessage()) . '</div>' .
|
||||
'Things you can try:' .
|
||||
'<ul>' .
|
||||
'<li>Use a different scraper</li>' .
|
||||
'<li>Remove keywords that could cause errors</li>' .
|
||||
'<li>Use another 4get instance</li>' .
|
||||
'</ul><br>' .
|
||||
'If the error persists, please <a href="/about">contact the administrator</a>.'
|
||||
);
|
||||
die();
|
||||
}
|
||||
|
||||
$categories = [
|
||||
"song" => "",
|
||||
"author" => "",
|
||||
"playlist" => ""
|
||||
];
|
||||
|
||||
/*
|
||||
Set the main container
|
||||
*/
|
||||
$main = null;
|
||||
|
||||
if(count($results["song"]) !== 0){
|
||||
|
||||
$main = "song";
|
||||
|
||||
}elseif(count($results["author"]) !== 0){
|
||||
|
||||
$main = "author";
|
||||
|
||||
}elseif(count($results["playlist"]) !== 0){
|
||||
|
||||
$main = "playlist";
|
||||
|
||||
}else{
|
||||
|
||||
// No results found!
|
||||
echo
|
||||
$frontend->drawerror(
|
||||
"Nobody here but us chickens!",
|
||||
'Have you tried:' .
|
||||
'<ul>' .
|
||||
'<li>Using a different scraper</li>' .
|
||||
'<li>Using fewer keywords</li>' .
|
||||
'<li>Defining broader filters (Is NSFW turned off?)</li>' .
|
||||
'</ul>' .
|
||||
'</div>'
|
||||
);
|
||||
die();
|
||||
}
|
||||
|
||||
/*
|
||||
Generate list of songs
|
||||
*/
|
||||
foreach($categories as $name => $data){
|
||||
|
||||
foreach($results[$name] as $item){
|
||||
|
||||
$greentext = [];
|
||||
|
||||
if(
|
||||
isset($item["date"]) &&
|
||||
$item["date"] !== null
|
||||
){
|
||||
|
||||
$greentext[] = date("jS M y @ g:ia", $item["date"]);
|
||||
}
|
||||
|
||||
if(
|
||||
isset($item["views"]) &&
|
||||
$item["views"] !== null
|
||||
){
|
||||
|
||||
$views = number_format($item["views"]) . " views";
|
||||
$greentext[] = $views;
|
||||
}
|
||||
|
||||
if(
|
||||
isset($item["followers"]) &&
|
||||
$item["followers"] !== null
|
||||
){
|
||||
|
||||
$greentext[] = number_format($item["followers"]) . " followers";
|
||||
}
|
||||
|
||||
if(
|
||||
isset($item["author"]["name"]) &&
|
||||
$item["author"]["name"] !== null
|
||||
){
|
||||
|
||||
$greentext[] = $item["author"]["name"];
|
||||
}
|
||||
|
||||
$greentext = implode(" • ", $greentext);
|
||||
|
||||
if(
|
||||
isset($item["duration"]) &&
|
||||
$item["duration"] !== null
|
||||
){
|
||||
|
||||
$duration = $frontend->s_to_timestamp($item["duration"]);
|
||||
}else{
|
||||
|
||||
$duration = null;
|
||||
}
|
||||
|
||||
$tabindex = $name == $main ? true : false;
|
||||
|
||||
$customhtml = null;
|
||||
|
||||
if(
|
||||
$name == "song" &&
|
||||
$item["stream"]["endpoint"] !== null
|
||||
){
|
||||
|
||||
$customhtml =
|
||||
'<audio src="' . $item["stream"]["endpoint"] . '?s=' . urlencode($item["stream"]["url"]) . '" controls autostart="false" preload="none">';
|
||||
}
|
||||
|
||||
$categories[$name] .= $frontend->drawtextresult($item, $greentext, $duration, $get["s"], $tabindex, $customhtml);
|
||||
}
|
||||
}
|
||||
|
||||
$payload["left"] = $categories[$main];
|
||||
|
||||
// dont re-draw the category
|
||||
unset($categories[$main]);
|
||||
|
||||
/*
|
||||
Populate right handside
|
||||
*/
|
||||
|
||||
$i = 1;
|
||||
foreach($categories as $name => $value){
|
||||
|
||||
if($value == ""){
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if($i % 2 === 1){
|
||||
|
||||
$write = "right-left";
|
||||
}else{
|
||||
|
||||
$write = "right-right";
|
||||
}
|
||||
|
||||
$payload[$write] .=
|
||||
'<div class="answer-wrapper">' .
|
||||
'<input id="answer' . $i . '" class="spoiler" type="checkbox">' .
|
||||
'<div class="answer">' .
|
||||
'<div class="answer-title">' .
|
||||
'<a class="answer-title" href="?s=' . urlencode($get["s"]);
|
||||
|
||||
switch($name){
|
||||
|
||||
case "playlist":
|
||||
$payload[$write] .=
|
||||
'&type=playlist"><h2>Playlists</h2></a>';
|
||||
break;
|
||||
|
||||
case "author":
|
||||
$payload[$write] .=
|
||||
'&type=people"><h2>Authors</h2></a>';
|
||||
break;
|
||||
}
|
||||
|
||||
$payload[$write] .=
|
||||
'</div>' .
|
||||
$categories[$name] .
|
||||
'</div>' .
|
||||
'<label class="spoiler-button" for="answer' . $i . '"></label></div>';
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($i !== 1){
|
||||
|
||||
$payload["class"] = " has-answer";
|
||||
}
|
||||
|
||||
if($results["npt"] !== null){
|
||||
|
||||
$payload["left"] .=
|
||||
'<a href="' . $frontend->htmlnextpage($get, $results["npt"], "music") . '" class="nextpage">Next page ></a>';
|
||||
}
|
||||
|
||||
echo $frontend->load("search.html", $payload);
|
397
scraper/sc.php
Normal file
397
scraper/sc.php
Normal file
|
@ -0,0 +1,397 @@
|
|||
<?php
|
||||
|
||||
class sc{
|
||||
|
||||
public function __construct(){
|
||||
|
||||
include "lib/nextpage.php";
|
||||
$this->nextpage = new nextpage("sc");
|
||||
}
|
||||
|
||||
public function getfilters($page){
|
||||
|
||||
return [
|
||||
"type" => [
|
||||
"display" => "Type",
|
||||
"option" => [
|
||||
"any" => "Any type",
|
||||
"track" => "Tracks",
|
||||
"people" => "People",
|
||||
"album" => "Albums",
|
||||
"playlist" => "Playlists",
|
||||
"goplus" => "Go+ Tracks"
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function get($url, $get = []){
|
||||
|
||||
$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
|
||||
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
|
||||
["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
|
||||
"Accept: application/json, text/javascript, */*; q=0.01",
|
||||
"Accept-Language: en-US,en;q=0.5",
|
||||
"Accept-Encoding: gzip",
|
||||
"Referer: https://soundcloud.com/",
|
||||
"Origin: https://soundcloud.com",
|
||||
"DNT: 1",
|
||||
"Connection: keep-alive",
|
||||
"Sec-Fetch-Dest: empty",
|
||||
"Sec-Fetch-Mode: cors",
|
||||
"Sec-Fetch-Site: same-site"]
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function music($get){
|
||||
|
||||
if($get["npt"]){
|
||||
|
||||
$params = $this->nextpage->get($get["npt"], "music");
|
||||
$params = json_decode($params, true);
|
||||
|
||||
$url = $params["url"];
|
||||
unset($params["url"]);
|
||||
|
||||
}else{
|
||||
|
||||
// normal search:
|
||||
// https://api-v2.soundcloud.com/search?q=freddie%20dredd&variant_ids=&facet=model&user_id=351062-302234-707916-795081&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
// soundcloud go+ search:
|
||||
// https://api-v2.soundcloud.com/search/tracks?q=freddie%20dredd&variant_ids=&filter.content_tier=SUB_HIGH_TIER&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
// tracks search:
|
||||
// https://api-v2.soundcloud.com/search/tracks?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
// users search:
|
||||
// https://api-v2.soundcloud.com/search/users?q=freddie%20dredd&variant_ids=&facet=place&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
// albums search:
|
||||
// https://api-v2.soundcloud.com/search/albums?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
// playlists search:
|
||||
// https://api-v2.soundcloud.com/search/playlists_without_albums?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
|
||||
|
||||
$search = $get["s"];
|
||||
$type = $get["type"];
|
||||
|
||||
switch($type){
|
||||
|
||||
case "any":
|
||||
$url = "https://api-v2.soundcloud.com/search";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"facet" => "model",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
|
||||
case "track":
|
||||
$url = "https://api-v2.soundcloud.com/search/tracks";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"facet_genre" => "",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
|
||||
case "people":
|
||||
$url = "https://api-v2.soundcloud.com/search/users";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"facet" => "place",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
|
||||
case "album":
|
||||
$url = "https://api-v2.soundcloud.com/search/albums";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"facet" => "genre",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
|
||||
case "playlist":
|
||||
$url = "https://api-v2.soundcloud.com/search/playlists_without_albums";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"facet" => "genre",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
|
||||
case "goplus":
|
||||
$url = "https://api-v2.soundcloud.com/search/tracks";
|
||||
$params = [
|
||||
"q" => $search,
|
||||
"variant_ids" => "",
|
||||
"filter.content_tier" => "SUB_HIGH_TIER",
|
||||
"facet" => "genre",
|
||||
"user_id" => "351062-302234-707916-795081",
|
||||
"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
|
||||
"limit" => 20,
|
||||
"offset" => 0,
|
||||
"linked_partitioning" => 1,
|
||||
"app_version" => 1693487844,
|
||||
"app_locale" => "en"
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
$json = $this->get($url, $params);
|
||||
|
||||
}catch(Exception $error){
|
||||
|
||||
throw new Exception("Failed to fetch JSON");
|
||||
}
|
||||
|
||||
/*
|
||||
$handle = fopen("scraper/soundcloud.json", "r");
|
||||
$json = fread($handle, filesize("scraper/soundcloud.json"));
|
||||
fclose($handle);
|
||||
*/
|
||||
|
||||
$json = json_decode($json, true);
|
||||
|
||||
if($json === null){
|
||||
|
||||
throw new Exception("Failed to decode JSON");
|
||||
}
|
||||
|
||||
$out = [
|
||||
"status" => "ok",
|
||||
"npt" => null,
|
||||
"song" => [],
|
||||
"playlist" => [],
|
||||
"author" => []
|
||||
];
|
||||
|
||||
/*
|
||||
Get next page
|
||||
*/
|
||||
if(isset($json["next_href"])){
|
||||
|
||||
$params["query_urn"] = $json["query_urn"];
|
||||
$params["offset"] = $params["offset"] + 20;
|
||||
$params["url"] = $url; // we will remove this later
|
||||
|
||||
$out["npt"] =
|
||||
$this->nextpage->store(
|
||||
json_encode($params),
|
||||
"music"
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Scrape items
|
||||
*/
|
||||
foreach($json["collection"] as $item){
|
||||
|
||||
switch($item["kind"]){
|
||||
|
||||
case "user":
|
||||
// parse author
|
||||
$out["author"][] = [
|
||||
"title" => $item["username"],
|
||||
"followers" => $item["followers_count"],
|
||||
"description" => $item["track_count"] . " songs. " . $this->limitstrlen($item["description"]),
|
||||
"thumb" => [
|
||||
"url" => $item["avatar_url"],
|
||||
"ratio" => "1:1"
|
||||
],
|
||||
"url" => $item["permalink_url"]
|
||||
];
|
||||
break;
|
||||
|
||||
case "playlist":
|
||||
// parse playlist
|
||||
$description = [];
|
||||
$count = 0;
|
||||
|
||||
foreach($item["tracks"] as $song){
|
||||
|
||||
$count++;
|
||||
|
||||
if(!isset($song["title"])){
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$description[] = $song["title"];
|
||||
}
|
||||
|
||||
if(count($description) != 0){
|
||||
|
||||
$description = $count . " songs. " . implode(", ", $description);
|
||||
}
|
||||
|
||||
if(
|
||||
isset($item["artwork_url"]) &&
|
||||
!empty($item["artwork_url"])
|
||||
){
|
||||
|
||||
$thumb = [
|
||||
"ratio" => "1:1",
|
||||
"url" => $item["artwork_url"]
|
||||
];
|
||||
|
||||
}elseif(
|
||||
isset($item["tracks"][0]["artwork_url"]) &&
|
||||
!empty($item["tracks"][0]["artwork_url"])
|
||||
){
|
||||
|
||||
$thumb = [
|
||||
"ratio" => "1:1",
|
||||
"url" => $item["tracks"][0]["artwork_url"]
|
||||
];
|
||||
}else{
|
||||
|
||||
$thumb = [
|
||||
"ratio" => null,
|
||||
"url" => null
|
||||
];
|
||||
}
|
||||
|
||||
$out["playlist"][] = [
|
||||
"title" => $item["title"],
|
||||
"description" => $description,
|
||||
"author" => [
|
||||
"name" => $item["user"]["username"],
|
||||
"url" => $item["user"]["permalink_url"],
|
||||
"avatar" => $item["user"]["avatar_url"]
|
||||
],
|
||||
"thumb" => $thumb,
|
||||
"date" => strtotime($item["created_at"]),
|
||||
"duration" => $item["duration"] / 1000,
|
||||
"url" => $item["permalink_url"]
|
||||
];
|
||||
break;
|
||||
|
||||
case "track":
|
||||
if(stripos($item["monetization_model"], "TIER") === false){
|
||||
|
||||
$stream = [
|
||||
"endpoint" => "audio_sc",
|
||||
"url" =>
|
||||
$item["media"]["transcodings"][0]["url"] .
|
||||
"?client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E" .
|
||||
"&track_authorization=" .
|
||||
$item["track_authorization"]
|
||||
];
|
||||
}else{
|
||||
|
||||
$stream = [
|
||||
"endpoint" => null,
|
||||
"url" => null
|
||||
];
|
||||
}
|
||||
|
||||
// parse track
|
||||
$out["song"][] = [
|
||||
"title" => $item["title"],
|
||||
"description" => $item["description"] == "" ? null : $this->limitstrlen($item["description"]),
|
||||
"url" => $item["permalink_url"],
|
||||
"views" => $item["playback_count"],
|
||||
"author" => [
|
||||
"name" => $item["user"]["username"],
|
||||
"url" => $item["user"]["permalink_url"],
|
||||
"avatar" => $item["user"]["avatar_url"]
|
||||
],
|
||||
"thumb" => [
|
||||
"ratio" => "1:1",
|
||||
"url" => $item["artwork_url"]
|
||||
],
|
||||
"date" => strtotime($item["created_at"]),
|
||||
"duration" => (int)$item["full_duration"] / 1000,
|
||||
"stream" => $stream
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function limitstrlen($text){
|
||||
|
||||
return
|
||||
explode(
|
||||
"\n",
|
||||
wordwrap(
|
||||
str_replace(
|
||||
"\n",
|
||||
" ",
|
||||
$text
|
||||
),
|
||||
300,
|
||||
"\n"
|
||||
)
|
||||
)[0];
|
||||
}
|
||||
}
|
|
@ -959,6 +959,7 @@ class yandex{
|
|||
"img"
|
||||
);
|
||||
|
||||
$c = 1;
|
||||
if(count($thumb) === 0){
|
||||
|
||||
$thumb = [
|
||||
|
@ -967,7 +968,6 @@ class yandex{
|
|||
];
|
||||
}else{
|
||||
|
||||
$c = 1;
|
||||
$thumb = [
|
||||
"url" =>
|
||||
str_replace(
|
||||
|
@ -1065,12 +1065,17 @@ class yandex{
|
|||
"views" => $views,
|
||||
"thumb" => $thumb,
|
||||
"url" =>
|
||||
str_replace(
|
||||
"http://",
|
||||
"https://",
|
||||
$this->fuckhtml
|
||||
->getTextContent(
|
||||
$data["counters"]
|
||||
["toHostingLoaded"]
|
||||
["postfix"]
|
||||
["href"]
|
||||
),
|
||||
$c
|
||||
)
|
||||
];
|
||||
}
|
||||
|
|
12
settings.php
12
settings.php
|
@ -161,6 +161,16 @@ $settings = [
|
|||
"text" => "Mojeek"
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"description" => "Music",
|
||||
"parameter" => "scraper_music",
|
||||
"options" => [
|
||||
[
|
||||
"value" => "sc",
|
||||
"text" => "SoundCloud"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -235,7 +245,7 @@ echo
|
|||
'<head>' .
|
||||
'<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' .
|
||||
'<title>Settings</title>' .
|
||||
'<link rel="stylesheet" href="/static/style.css">' .
|
||||
'<link rel="stylesheet" href="/static/style.css?v3">' .
|
||||
'<meta name="viewport" content="width=device-width,initial-scale=1">' .
|
||||
'<meta name="robots" content="index,follow">' .
|
||||
'<link rel="icon" type="image/x-icon" href="/favicon.ico">' .
|
||||
|
|
|
@ -51,6 +51,13 @@
|
|||
audio{
|
||||
max-width:100%;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.left audio{
|
||||
margin-top:7px;
|
||||
}
|
||||
|
||||
.right-wrapper audio{
|
||||
margin-bottom:17px;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
<title>{%title%}</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v2">
|
||||
<link rel="stylesheet" href="/static/style.css?v3">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="robots" content="{%index%}index,{%index%}follow">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
<title>4get</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="/static/style.css?v2">
|
||||
<link rel="stylesheet" href="/static/style.css?v3">
|
||||
<meta name="robots" content="index,follow">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta name="description" content="4get.ca: They live in our walls!">
|
||||
|
|
Reference in a new issue