sc.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <?php
  2. class sc{
  3. public function __construct(){
  4. include "lib/nextpage.php";
  5. $this->nextpage = new nextpage("sc");
  6. }
  7. public function getfilters($page){
  8. return [
  9. "type" => [
  10. "display" => "Type",
  11. "option" => [
  12. "any" => "Any type",
  13. "track" => "Tracks",
  14. "people" => "People",
  15. "album" => "Albums",
  16. "playlist" => "Playlists",
  17. "goplus" => "Go+ Tracks"
  18. ]
  19. ]
  20. ];
  21. }
  22. private function get($url, $get = []){
  23. $curlproc = curl_init();
  24. if($get !== []){
  25. $get = http_build_query($get);
  26. $url .= "?" . $get;
  27. }
  28. curl_setopt($curlproc, CURLOPT_URL, $url);
  29. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  30. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  31. ["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0",
  32. "Accept: application/json, text/javascript, */*; q=0.01",
  33. "Accept-Language: en-US,en;q=0.5",
  34. "Accept-Encoding: gzip",
  35. "Referer: https://soundcloud.com/",
  36. "Origin: https://soundcloud.com",
  37. "DNT: 1",
  38. "Connection: keep-alive",
  39. "Sec-Fetch-Dest: empty",
  40. "Sec-Fetch-Mode: cors",
  41. "Sec-Fetch-Site: same-site"]
  42. );
  43. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  44. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  45. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  46. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  47. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  48. $data = curl_exec($curlproc);
  49. if(curl_errno($curlproc)){
  50. throw new Exception(curl_error($curlproc));
  51. }
  52. curl_close($curlproc);
  53. return $data;
  54. }
  55. public function music($get){
  56. if($get["npt"]){
  57. $params = $this->nextpage->get($get["npt"], "music");
  58. $params = json_decode($params, true);
  59. $url = $params["url"];
  60. unset($params["url"]);
  61. }else{
  62. // normal search:
  63. // 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
  64. // soundcloud go+ search:
  65. // 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
  66. // tracks search:
  67. // 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
  68. // users search:
  69. // 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
  70. // albums search:
  71. // 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
  72. // playlists search:
  73. // 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
  74. $search = $get["s"];
  75. $type = $get["type"];
  76. switch($type){
  77. case "any":
  78. $url = "https://api-v2.soundcloud.com/search";
  79. $params = [
  80. "q" => $search,
  81. "variant_ids" => "",
  82. "facet" => "model",
  83. "user_id" => "351062-302234-707916-795081",
  84. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  85. "limit" => 20,
  86. "offset" => 0,
  87. "linked_partitioning" => 1,
  88. "app_version" => 1693487844,
  89. "app_locale" => "en"
  90. ];
  91. break;
  92. case "track":
  93. $url = "https://api-v2.soundcloud.com/search/tracks";
  94. $params = [
  95. "q" => $search,
  96. "variant_ids" => "",
  97. "facet_genre" => "",
  98. "user_id" => "351062-302234-707916-795081",
  99. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  100. "limit" => 20,
  101. "offset" => 0,
  102. "linked_partitioning" => 1,
  103. "app_version" => 1693487844,
  104. "app_locale" => "en"
  105. ];
  106. break;
  107. case "people":
  108. $url = "https://api-v2.soundcloud.com/search/users";
  109. $params = [
  110. "q" => $search,
  111. "variant_ids" => "",
  112. "facet" => "place",
  113. "user_id" => "351062-302234-707916-795081",
  114. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  115. "limit" => 20,
  116. "offset" => 0,
  117. "linked_partitioning" => 1,
  118. "app_version" => 1693487844,
  119. "app_locale" => "en"
  120. ];
  121. break;
  122. case "album":
  123. $url = "https://api-v2.soundcloud.com/search/albums";
  124. $params = [
  125. "q" => $search,
  126. "variant_ids" => "",
  127. "facet" => "genre",
  128. "user_id" => "351062-302234-707916-795081",
  129. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  130. "limit" => 20,
  131. "offset" => 0,
  132. "linked_partitioning" => 1,
  133. "app_version" => 1693487844,
  134. "app_locale" => "en"
  135. ];
  136. break;
  137. case "playlist":
  138. $url = "https://api-v2.soundcloud.com/search/playlists_without_albums";
  139. $params = [
  140. "q" => $search,
  141. "variant_ids" => "",
  142. "facet" => "genre",
  143. "user_id" => "351062-302234-707916-795081",
  144. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  145. "limit" => 20,
  146. "offset" => 0,
  147. "linked_partitioning" => 1,
  148. "app_version" => 1693487844,
  149. "app_locale" => "en"
  150. ];
  151. break;
  152. case "goplus":
  153. $url = "https://api-v2.soundcloud.com/search/tracks";
  154. $params = [
  155. "q" => $search,
  156. "variant_ids" => "",
  157. "filter.content_tier" => "SUB_HIGH_TIER",
  158. "facet" => "genre",
  159. "user_id" => "351062-302234-707916-795081",
  160. "client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E",
  161. "limit" => 20,
  162. "offset" => 0,
  163. "linked_partitioning" => 1,
  164. "app_version" => 1693487844,
  165. "app_locale" => "en"
  166. ];
  167. break;
  168. }
  169. }
  170. try{
  171. $json = $this->get($url, $params);
  172. }catch(Exception $error){
  173. throw new Exception("Failed to fetch JSON");
  174. }
  175. /*
  176. $handle = fopen("scraper/soundcloud.json", "r");
  177. $json = fread($handle, filesize("scraper/soundcloud.json"));
  178. fclose($handle);
  179. */
  180. $json = json_decode($json, true);
  181. if($json === null){
  182. throw new Exception("Failed to decode JSON");
  183. }
  184. $out = [
  185. "status" => "ok",
  186. "npt" => null,
  187. "song" => [],
  188. "playlist" => [],
  189. "author" => []
  190. ];
  191. /*
  192. Get next page
  193. */
  194. if(isset($json["next_href"])){
  195. $params["query_urn"] = $json["query_urn"];
  196. $params["offset"] = $params["offset"] + 20;
  197. $params["url"] = $url; // we will remove this later
  198. $out["npt"] =
  199. $this->nextpage->store(
  200. json_encode($params),
  201. "music"
  202. );
  203. }
  204. /*
  205. Scrape items
  206. */
  207. foreach($json["collection"] as $item){
  208. switch($item["kind"]){
  209. case "user":
  210. // parse author
  211. $out["author"][] = [
  212. "title" => $item["username"],
  213. "followers" => $item["followers_count"],
  214. "description" => $item["track_count"] . " songs. " . $this->limitstrlen($item["description"]),
  215. "thumb" => [
  216. "url" => $item["avatar_url"],
  217. "ratio" => "1:1"
  218. ],
  219. "url" => $item["permalink_url"]
  220. ];
  221. break;
  222. case "playlist":
  223. // parse playlist
  224. $description = [];
  225. $count = 0;
  226. foreach($item["tracks"] as $song){
  227. $count++;
  228. if(!isset($song["title"])){
  229. continue;
  230. }
  231. $description[] = $song["title"];
  232. }
  233. if(count($description) != 0){
  234. $description = $count . " songs. " . implode(", ", $description);
  235. }
  236. if(
  237. isset($item["artwork_url"]) &&
  238. !empty($item["artwork_url"])
  239. ){
  240. $thumb = [
  241. "ratio" => "1:1",
  242. "url" => $item["artwork_url"]
  243. ];
  244. }elseif(
  245. isset($item["tracks"][0]["artwork_url"]) &&
  246. !empty($item["tracks"][0]["artwork_url"])
  247. ){
  248. $thumb = [
  249. "ratio" => "1:1",
  250. "url" => $item["tracks"][0]["artwork_url"]
  251. ];
  252. }else{
  253. $thumb = [
  254. "ratio" => null,
  255. "url" => null
  256. ];
  257. }
  258. $out["playlist"][] = [
  259. "title" => $item["title"],
  260. "description" => $description,
  261. "author" => [
  262. "name" => $item["user"]["username"],
  263. "url" => $item["user"]["permalink_url"],
  264. "avatar" => $item["user"]["avatar_url"]
  265. ],
  266. "thumb" => $thumb,
  267. "date" => strtotime($item["created_at"]),
  268. "duration" => $item["duration"] / 1000,
  269. "url" => $item["permalink_url"]
  270. ];
  271. break;
  272. case "track":
  273. if(stripos($item["monetization_model"], "TIER") === false){
  274. $stream = [
  275. "endpoint" => "audio_sc",
  276. "url" =>
  277. $item["media"]["transcodings"][0]["url"] .
  278. "?client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E" .
  279. "&track_authorization=" .
  280. $item["track_authorization"]
  281. ];
  282. }else{
  283. $stream = [
  284. "endpoint" => null,
  285. "url" => null
  286. ];
  287. }
  288. // parse track
  289. $out["song"][] = [
  290. "title" => $item["title"],
  291. "description" => $item["description"] == "" ? null : $this->limitstrlen($item["description"]),
  292. "url" => $item["permalink_url"],
  293. "views" => $item["playback_count"],
  294. "author" => [
  295. "name" => $item["user"]["username"],
  296. "url" => $item["user"]["permalink_url"],
  297. "avatar" => $item["user"]["avatar_url"]
  298. ],
  299. "thumb" => [
  300. "ratio" => "1:1",
  301. "url" => $item["artwork_url"]
  302. ],
  303. "date" => strtotime($item["created_at"]),
  304. "duration" => (int)$item["full_duration"] / 1000,
  305. "stream" => $stream
  306. ];
  307. break;
  308. }
  309. }
  310. return $out;
  311. }
  312. private function limitstrlen($text){
  313. return
  314. explode(
  315. "\n",
  316. wordwrap(
  317. str_replace(
  318. "\n",
  319. " ",
  320. $text
  321. ),
  322. 300,
  323. "\n"
  324. )
  325. )[0];
  326. }
  327. }