soundcloud lolllllll

This commit is contained in:
lolcat 2023-09-03 22:41:44 -04:00
parent cfd44438ae
commit c8ab934b10
13 changed files with 982 additions and 18 deletions

23
.gitignore vendored Normal file
View 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
View File

@ -242,6 +242,21 @@
the endpoint above. 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 + /favicon
Get the favicon for a website. The only parameter is "s", and must Get the favicon for a website. The only parameter is "s", and must
include the protocol. include the protocol.
@ -284,6 +299,20 @@
The parameter is "s" for the audio link. 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 + Appendix
If you have any questions or need clarifications, please send an If you have any questions or need clarifications, please send an
email my way to will at lolcat.ca email my way to will at lolcat.ca

26
api/v1/music.php Normal file
View 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
View 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();
}
}

View File

@ -150,7 +150,7 @@ class proxy{
$curl, $curl,
CURLOPT_HTTPHEADER, 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: 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-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate", "Accept-Encoding: gzip, deflate",
@ -178,7 +178,7 @@ class proxy{
$curl, $curl,
CURLOPT_HTTPHEADER, 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: image/avif,image/webp,*/*",
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate", "Accept-Encoding: gzip, deflate",
@ -380,7 +380,7 @@ class proxy{
$curl, $curl,
CURLOPT_HTTPHEADER, 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: image/avif,image/webp,*/*",
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br", "Accept-Encoding: gzip, deflate, br",
@ -396,7 +396,7 @@ class proxy{
$curl, $curl,
CURLOPT_HTTPHEADER, 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: 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-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br", "Accept-Encoding: gzip, deflate, br",

View File

@ -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 = $payload =
'<div class="text-result">'; '<div class="text-result">';
@ -188,6 +188,8 @@ class frontend{
'</div>'; '</div>';
} }
$payload .= $customhtml;
$payload .= '</a>'; $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://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://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://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://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>' . '<a href="https://megalodon.jp/?url=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://megalodon.jp" alt="me">Megalodon</a>' .
'</div>'; '</div>';
@ -835,6 +838,10 @@ class frontend{
case "news": case "news":
$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null; $get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null;
break; break;
case "music":
$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null;
break;
} }
if( if(
@ -923,6 +930,14 @@ class frontend{
"mojeek" => "Mojeek" "mojeek" => "Mojeek"
] ]
]; ];
case "music":
$filters["scraper"] = [
"display" => "Scraper",
"option" => [
"sc" => "SoundCloud"
]
];
break; break;
} }
@ -994,6 +1009,11 @@ class frontend{
include "scraper/wiby.php"; include "scraper/wiby.php";
$lib = new wiby(); $lib = new wiby();
break; break;
case "sc":
include "scraper/sc.php";
$lib = new sc();
break;
} }
// set scraper on $_GET // set scraper on $_GET
@ -1169,7 +1189,7 @@ class frontend{
$html = null; $html = null;
foreach(["web", "images", "videos", "news"] as $type){ foreach(["web", "images", "videos", "news", "music"] as $type){
$html .= '<a href="/' . $type . '?s=' . urlencode($query); $html .= '<a href="/' . $type . '?s=' . urlencode($query);
@ -1303,7 +1323,7 @@ class frontend{
return htmlspecialchars($image); return htmlspecialchars($image);
} }
return "/proxy?i=" . urlencode($image) . "&s=" . $format; return "/proxy.php?i=" . urlencode($image) . "&s=" . $format;
} }
public function htmlnextpage($gets, $npt, $page){ public function htmlnextpage($gets, $npt, $page){

224
music.php Normal file
View 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 &gt;</a>';
}
echo $frontend->load("search.html", $payload);

397
scraper/sc.php Normal file
View 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];
}
}

View File

@ -959,6 +959,7 @@ class yandex{
"img" "img"
); );
$c = 1;
if(count($thumb) === 0){ if(count($thumb) === 0){
$thumb = [ $thumb = [
@ -967,7 +968,6 @@ class yandex{
]; ];
}else{ }else{
$c = 1;
$thumb = [ $thumb = [
"url" => "url" =>
str_replace( str_replace(
@ -1065,12 +1065,17 @@ class yandex{
"views" => $views, "views" => $views,
"thumb" => $thumb, "thumb" => $thumb,
"url" => "url" =>
$this->fuckhtml str_replace(
->getTextContent( "http://",
$data["counters"] "https://",
["toHostingLoaded"] $this->fuckhtml
["postfix"] ->getTextContent(
["href"] $data["counters"]
["toHostingLoaded"]
["postfix"]
["href"]
),
$c
) )
]; ];
} }

View File

@ -161,6 +161,16 @@ $settings = [
"text" => "Mojeek" "text" => "Mojeek"
] ]
] ]
],
[
"description" => "Music",
"parameter" => "scraper_music",
"options" => [
[
"value" => "sc",
"text" => "SoundCloud"
]
]
] ]
] ]
] ]
@ -235,7 +245,7 @@ echo
'<head>' . '<head>' .
'<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' . '<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' .
'<title>Settings</title>' . '<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="viewport" content="width=device-width,initial-scale=1">' .
'<meta name="robots" content="index,follow">' . '<meta name="robots" content="index,follow">' .
'<link rel="icon" type="image/x-icon" href="/favicon.ico">' . '<link rel="icon" type="image/x-icon" href="/favicon.ico">' .

View File

@ -51,6 +51,13 @@
audio{ audio{
max-width:100%; max-width:100%;
display:block; display:block;
}
.left audio{
margin-top:7px;
}
.right-wrapper audio{
margin-bottom:17px; margin-bottom:17px;
} }

View File

@ -3,7 +3,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>{%title%}</title> <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="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="{%index%}index,{%index%}follow"> <meta name="robots" content="{%index%}index,{%index%}follow">
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">

View File

@ -4,7 +4,7 @@
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>4get</title> <title>4get</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <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"> <meta name="robots" content="index,follow">
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
<meta name="description" content="4get.ca: They live in our walls!"> <meta name="description" content="4get.ca: They live in our walls!">