spotify.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. include "../data/config.php";
  3. new spotify();
  4. class spotify{
  5. public function __construct(){
  6. include "../lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. if(
  9. !isset($_GET["s"]) ||
  10. !preg_match(
  11. '/^(track|episode)\.([A-Za-z0-9]{22})$/',
  12. $_GET["s"],
  13. $matches
  14. )
  15. ){
  16. $this->do404("The track ID(s) parameter is missing or invalid");
  17. }
  18. try{
  19. if($matches[1] == "episode"){
  20. $uri = "show";
  21. }else{
  22. $uri = $matches[1];
  23. }
  24. $embed =
  25. $this->get("https://embed.spotify.com/{$uri}/" . $matches[2]);
  26. }catch(Exception $error){
  27. $this->do404("Failed to fetch embed data");
  28. }
  29. $this->fuckhtml->load($embed);
  30. $json =
  31. $this->fuckhtml
  32. ->getElementById(
  33. "__NEXT_DATA__",
  34. "script"
  35. );
  36. if($json === null){
  37. $this->do404("Failed to extract JSON");
  38. }
  39. $json =
  40. json_decode($json["innerHTML"], true);
  41. if($json === null){
  42. $this->do404("Failed to decode JSON");
  43. }
  44. switch($matches[1]){
  45. case "track":
  46. if(
  47. isset(
  48. $json
  49. ["props"]
  50. ["pageProps"]
  51. ["state"]
  52. ["data"]
  53. ["entity"]
  54. ["audioPreview"]
  55. ["url"]
  56. )
  57. ){
  58. header("Content-type: audio/mpeg");
  59. header(
  60. "Location: /audio/linear?s=" .
  61. urlencode(
  62. $json
  63. ["props"]
  64. ["pageProps"]
  65. ["state"]
  66. ["data"]
  67. ["entity"]
  68. ["audioPreview"]
  69. ["url"]
  70. )
  71. );
  72. }else{
  73. $this->do404("Could not extract playback URL");
  74. }
  75. break;
  76. case "episode":
  77. if(
  78. isset(
  79. $json
  80. ["props"]
  81. ["pageProps"]
  82. ["state"]
  83. ["data"]
  84. ["entity"]
  85. ["id"]
  86. )
  87. ){
  88. try{
  89. $json =
  90. $this->get(
  91. "https://spclient.wg.spotify.com/soundfinder/v1/unauth/episode/" .
  92. $json
  93. ["props"]
  94. ["pageProps"]
  95. ["state"]
  96. ["data"]
  97. ["entity"]
  98. ["id"] .
  99. "/com.widevine.alpha"
  100. );
  101. }catch(Exception $error){
  102. $this->do404("Failed to fetch audio resource");
  103. }
  104. $json = json_decode($json, true);
  105. if($json === null){
  106. $this->do404("Failed to decode audio resource JSON");
  107. }
  108. if(
  109. isset($json["passthrough"]) &&
  110. $json["passthrough"] == "ALLOWED" &&
  111. isset($json["passthroughUrl"])
  112. ){
  113. header(
  114. "Location:" .
  115. "/audio/linear.php?s=" .
  116. urlencode(
  117. str_replace(
  118. "http://",
  119. "https://",
  120. $json["passthroughUrl"]
  121. )
  122. )
  123. );
  124. }else{
  125. $this->do404("Failed to find passthroughUrl");
  126. }
  127. }else{
  128. $this->do404("Failed to find episode ID");
  129. }
  130. break;
  131. }
  132. }
  133. private function get($url){
  134. $headers = [
  135. "User-Agent: " . config::USER_AGENT,
  136. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  137. "Accept-Language: en-US,en;q=0.5",
  138. "Accept-Encoding: gzip",
  139. "DNT: 1",
  140. "Connection: keep-alive",
  141. "Upgrade-Insecure-Requests: 1",
  142. "Sec-Fetch-Dest: document",
  143. "Sec-Fetch-Mode: navigate",
  144. "Sec-Fetch-Site: none",
  145. "Sec-Fetch-User: ?1"
  146. ];
  147. $curlproc = curl_init();
  148. curl_setopt($curlproc, CURLOPT_URL, $url);
  149. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  150. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  151. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  152. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  153. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  154. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  155. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  156. $data = curl_exec($curlproc);
  157. if(curl_errno($curlproc)){
  158. throw new Exception(curl_error($curlproc));
  159. }
  160. curl_close($curlproc);
  161. return $data;
  162. }
  163. private function do404($error){
  164. http_response_code(404);
  165. header("Content-Type: text/plain");
  166. header("X-Error: $error");
  167. die();
  168. }
  169. }