spotify.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. <?php
  2. class spotify{
  3. private const req_web = 0;
  4. private const req_api = 1;
  5. private const req_clientid = 2;
  6. public function __construct(){
  7. include "lib/backend.php";
  8. $this->backend = new backend("spotify");
  9. include "lib/fuckhtml.php";
  10. $this->fuckhtml = new fuckhtml();
  11. }
  12. public function getfilters($page){
  13. return [
  14. "category" => [
  15. "display" => "Category",
  16. "option" => [
  17. "any" => "All (no pagination)",
  18. "audiobooks" => "Audiobooks",
  19. "tracks" => "Songs",
  20. "artists" => "Artists",
  21. "playlists" => "Playlists",
  22. "albums" => "Albums",
  23. "podcastAndEpisodes" => "Podcasts & Shows (no pagination)",
  24. "episodes" => "Episodes",
  25. "users" => "Profiles"
  26. ]
  27. ]
  28. ];
  29. }
  30. private function get($proxy, $url, $get = [], $reqtype = self::req_web, $bearer = null, $token = null){
  31. $curlproc = curl_init();
  32. switch($reqtype){
  33. case self::req_api:
  34. $headers = [
  35. "User-Agent: " . config::USER_AGENT,
  36. "Accept: application/json",
  37. "Accept-Language: en",
  38. "app-platform: WebPlayer",
  39. "authorization: Bearer {$bearer}",
  40. "client-token: {$token}",
  41. "content-type: application/json;charset=UTF-8",
  42. "Origin: https://open.spotify.com",
  43. "Referer: https://open.spotify.com/",
  44. "DNT: 1",
  45. "Connection: keep-alive",
  46. "Sec-Fetch-Dest: empty",
  47. "Sec-Fetch-Mode: cors",
  48. "Sec-Fetch-Site: same-site",
  49. "spotify-app-version: 1.2.27.93.g7aee53d4",
  50. "TE: trailers"
  51. ];
  52. break;
  53. case self::req_web:
  54. $headers = [
  55. "User-Agent: " . config::USER_AGENT,
  56. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  57. "Accept-Language: en-US,en;q=0.5",
  58. "Accept-Encoding: gzip",
  59. "DNT: 1",
  60. "Sec-GPC: 1",
  61. "Connection: keep-alive",
  62. "Upgrade-Insecure-Requests: 1",
  63. "Sec-Fetch-Dest: document",
  64. "Sec-Fetch-Mode: navigate",
  65. "Sec-Fetch-Site: cross-site"
  66. ];
  67. break;
  68. case self::req_clientid:
  69. $get = json_encode($get);
  70. curl_setopt($curlproc, CURLOPT_POST, true);
  71. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
  72. $headers = [
  73. "User-Agent:" . config::USER_AGENT,
  74. "Accept: application/json",
  75. "Accept-Language: en-US,en;q=0.5",
  76. "Accept-Encoding: gzip, deflate, br",
  77. "Referer: https://open.spotify.com/",
  78. "content-type: application/json",
  79. "Content-Length: " . strlen($get),
  80. "Origin: https://open.spotify.com",
  81. "DNT: 1",
  82. "Sec-GPC: 1",
  83. "Connection: keep-alive",
  84. "Sec-Fetch-Dest: empty",
  85. "Sec-Fetch-Mode: cors",
  86. "Sec-Fetch-Site: same-site",
  87. "TE: trailers"
  88. ];
  89. break;
  90. }
  91. if($reqtype !== self::req_clientid){
  92. if($get !== []){
  93. $get = http_build_query($get);
  94. $url .= "?" . $get;
  95. }
  96. }
  97. curl_setopt($curlproc, CURLOPT_URL, $url);
  98. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  99. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  100. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  101. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  102. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  103. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  104. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  105. $this->backend->assign_proxy($curlproc, $proxy);
  106. $data = curl_exec($curlproc);
  107. if(curl_errno($curlproc)){
  108. throw new Exception(curl_error($curlproc));
  109. }
  110. curl_close($curlproc);
  111. return $data;
  112. }
  113. public function music($get){
  114. $search = $get["s"];
  115. $ip = $this->backend->get_ip();
  116. $category = $get["category"];
  117. /*
  118. audiobooks first and second page decoded
  119. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAudiobooks&variables={"searchTerm":"freddie+dredd","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"8758e540afdba5afa3c5246817f6bd31d86a15b3f5666c363dd017030f35d785"}}
  120. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAudiobooks&variables={"searchTerm":"freddie+dredd","offset":30,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"8758e540afdba5afa3c5246817f6bd31d86a15b3f5666c363dd017030f35d785"}}
  121. */
  122. /*
  123. songs
  124. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchTracks&variables={"searchTerm":"asmr","offset":0,"limit":100,"numberOfTopResults":20,"includeAudiobooks":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"16c02d6304f5f721fc2eb39dacf2361a4543815112506a9c05c9e0bc9733a679"}}
  125. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchTracks&variables={"searchTerm":"asmr","offset":100,"limit":100,"numberOfTopResults":20,"includeAudiobooks":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"16c02d6304f5f721fc2eb39dacf2361a4543815112506a9c05c9e0bc9733a679"}}
  126. */
  127. /*
  128. artists
  129. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
  130. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":30,"limit":23,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
  131. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":53,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
  132. */
  133. /*
  134. playlists
  135. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchPlaylists&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"19b4143a0500ccec189ca0f4a0316bc2c615ecb51ce993ba4d7d08afd1d87aa4"}}
  136. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchPlaylists&variables={"searchTerm":"asmr","offset":30,"limit":3,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"19b4143a0500ccec189ca0f4a0316bc2c615ecb51ce993ba4d7d08afd1d87aa4"}}
  137. */
  138. /*
  139. albums
  140. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAlbums&variables={"searchTerm":"asmr","offset":33,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"e93b13cda461482da2940467eb2beed9368e9bb2fff37df3fb6633fc61271a27"}}
  141. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAlbums&variables={"searchTerm":"asmr","offset":33,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"e93b13cda461482da2940467eb2beed9368e9bb2fff37df3fb6633fc61271a27"}}
  142. */
  143. /*
  144. podcasts & shows (contains authors, no pagination)
  145. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchFullEpisodes&variables={"searchTerm":"asmr","offset":0,"limit":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"9f996251c9781fabce63f1a9980b5287ea33bc5e8c8953d0c4689b09936067a1"}}
  146. */
  147. /*
  148. episodes
  149. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchDesktop&variables={"searchTerm":"asmr","offset":0,"limit":10,"numberOfTopResults":5,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"da03293d92a2cfc5e24597dcdc652c0ad135e1c64a78fddbf1478a7e096bea44"}}
  150. ??? https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchFullEpisodes&variables={"searchTerm":"asmr","offset":60,"limit":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"9f996251c9781fabce63f1a9980b5287ea33bc5e8c8953d0c4689b09936067a1"}}
  151. */
  152. /*
  153. profiles
  154. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchUsers&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"02026f48ab5001894e598904079b620ebc64f2d53b55ca20c3858abd3a46c5fb"}}
  155. https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchUsers&variables={"searchTerm":"asmr","offset":30,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"02026f48ab5001894e598904079b620ebc64f2d53b55ca20c3858abd3a46c5fb"}}
  156. */
  157. // get HTML
  158. try{
  159. $html =
  160. $this->get(
  161. $ip,
  162. "https://open.spotify.com/search/" .
  163. rawurlencode($search) .
  164. ($category != "any" ? "/" . $category : ""),
  165. []
  166. );
  167. }catch(Exception $error){
  168. throw new Exception("Failed to get initial search page");
  169. }
  170. // grep bearer and client ID
  171. $this->fuckhtml->load($html);
  172. $script =
  173. $this->fuckhtml
  174. ->getElementById(
  175. "session",
  176. "script"
  177. );
  178. if($script === null){
  179. throw new Exception("Failed to grep bearer token");
  180. }
  181. $script =
  182. json_decode(
  183. $script["innerHTML"],
  184. true
  185. );
  186. $bearer = $script["accessToken"];
  187. $client_id = $script["clientId"];
  188. // hit client ID endpoint
  189. try{
  190. $token =
  191. json_decode(
  192. $this->get(
  193. $ip,
  194. "https://clienttoken.spotify.com/v1/clienttoken",
  195. [ // !! that shit must be sent as json data
  196. "client_data" => [
  197. "client_id" => $client_id,
  198. "client_version" => "1.2.27.93.g7aee53d4",
  199. "js_sdk_data" => [
  200. "device_brand" => "unknown",
  201. "device_id" => "4c7ca20117ca12288ea8fc7118a9118c",
  202. "device_model" => "unknown",
  203. "device_name" => "computer",
  204. "os" => "windows",
  205. "os_version" => "NT 10.0"
  206. ]
  207. ]
  208. ],
  209. self::req_clientid
  210. ),
  211. true
  212. );
  213. }catch(Exception $error){
  214. throw new Exception("Failed to fetch token");
  215. }
  216. if($token === null){
  217. throw new Exception("Failed to decode token");
  218. }
  219. $token = $token["granted_token"]["token"];
  220. try{
  221. switch($get["option"]){
  222. case "any":
  223. $variables = [
  224. "searchTerm" => $search,
  225. "offset" => 0,
  226. "limit" => 10,
  227. "numberOfTopResults" => 5,
  228. "includeAudiobooks" => true
  229. ];
  230. break;
  231. case "audiobooks":
  232. break;
  233. }
  234. $payload =
  235. $this->get(
  236. $ip,
  237. "https://api-partner.spotify.com/pathfinder/v1/query",
  238. [
  239. "operationName" => "searchDesktop",
  240. "variables" =>
  241. json_encode(
  242. [
  243. "searchTerm" => $search,
  244. "offset" => 0,
  245. "limit" => 10,
  246. "numberOfTopResults" => 5,
  247. "includeAudiobooks" => true
  248. ]
  249. ),
  250. "extensions" =>
  251. json_encode(
  252. [
  253. "persistedQuery" => [
  254. "version" => 1,
  255. "sha256Hash" => "21969b655b795601fb2d2204a4243188e75fdc6d3520e7b9cd3f4db2aff9591e" // ?
  256. ]
  257. ]
  258. )
  259. ],
  260. self::req_api,
  261. $bearer,
  262. $token
  263. );
  264. }catch(Exception $error){
  265. throw new Exception("Failed to fetch JSON results");
  266. }
  267. if($payload == "Token expired"){
  268. throw new Exception("Grepped spotify token has expired");
  269. }
  270. $payload = json_decode($payload, true);
  271. if($payload === null){
  272. throw new Exception("Failed to decode JSON results");
  273. }
  274. //$payload = json_decode(file_get_contents("scraper/spotify.json"), true);
  275. $out = [
  276. "status" => "ok",
  277. "npt" => null,
  278. "song" => [],
  279. "playlist" => [],
  280. "album" => [],
  281. "podcast" => [],
  282. "author" => [],
  283. "user" => []
  284. ];
  285. // get songs
  286. foreach($payload["data"]["searchV2"]["tracksV2"]["items"] as $result){
  287. if(isset($result["item"])){
  288. $result = $result["item"];
  289. }
  290. if(isset($result["data"])){
  291. $result = $result["data"];
  292. }
  293. [$artist, $artist_link] = $this->get_artists($result["artists"]);
  294. $out["song"][] = [
  295. "title" => $result["name"],
  296. "description" => null,
  297. "url" => "https://open.spotify.com/track/" . $result["id"],
  298. "views" => null,
  299. "author" => [
  300. "name" => $artist,
  301. "url" => $artist_link,
  302. "avatar" => null
  303. ],
  304. "thumb" => $this->get_thumb($result["albumOfTrack"]["coverArt"]),
  305. "date" => null,
  306. "duration" => $result["duration"]["totalMilliseconds"] / 1000,
  307. "stream" => [
  308. "endpoint" => "spotify",
  309. "url" => "track." . $result["id"]
  310. ]
  311. ];
  312. }
  313. // get playlists
  314. foreach($payload["data"]["searchV2"]["playlists"]["items"] as $playlist){
  315. if(isset($playlist["data"])){
  316. $playlist = $playlist["data"];
  317. }
  318. $avatar = $this->get_thumb($playlist["ownerV2"]["data"]["avatar"]);
  319. $out["playlist"][] = [
  320. "title" => $playlist["name"],
  321. "description" => null,
  322. "author" => [
  323. "name" => $playlist["ownerV2"]["data"]["name"],
  324. "url" =>
  325. "https://open.spotify.com/user/" .
  326. explode(
  327. ":",
  328. $playlist["ownerV2"]["data"]["uri"],
  329. 3
  330. )[2],
  331. "avatar" => $avatar["url"]
  332. ],
  333. "thumb" => $this->get_thumb($playlist["images"]["items"][0]),
  334. "date" => null,
  335. "duration" => null,
  336. "url" =>
  337. "https://open.spotify.com/playlist/" .
  338. explode(
  339. ":",
  340. $playlist["uri"],
  341. 3
  342. )[2]
  343. ];
  344. }
  345. // get albums
  346. foreach($payload["data"]["searchV2"]["albums"]["items"] as $album){
  347. if(isset($album["data"])){
  348. $album = $album["data"];
  349. }
  350. [$artist, $artist_link] = $this->get_artists($album["artists"]);
  351. $out["album"][] = [
  352. "title" => $album["name"],
  353. "description" => null,
  354. "author" => [
  355. "name" => $artist,
  356. "url" => $artist_link,
  357. "avatar" => null
  358. ],
  359. "thumb" => $this->get_thumb($album["coverArt"]),
  360. "date" => mktime(0, 0, 0, 0, 32, $album["date"]["year"]),
  361. "duration" => null,
  362. "url" =>
  363. "https://open.spotify.com/album/" .
  364. explode(
  365. ":",
  366. $album["uri"],
  367. 3
  368. )[2]
  369. ];
  370. }
  371. // get podcasts
  372. foreach($payload["data"]["searchV2"]["podcasts"]["items"] as $podcast){
  373. if(isset($podcast["data"])){
  374. $podcast = $podcast["data"];
  375. }
  376. $description = [];
  377. foreach($podcast["topics"]["items"] as $subject){
  378. $description[] = $subject["title"];
  379. }
  380. $description = implode(", ", $description);
  381. if($description == ""){
  382. $description = null;
  383. }
  384. $out["podcast"][] = [
  385. "title" => $podcast["name"],
  386. "description" => $description,
  387. "author" => [
  388. "name" => $podcast["publisher"]["name"],
  389. "url" => null,
  390. "avatar" => null
  391. ],
  392. "thumb" => $this->get_thumb($podcast["coverArt"]),
  393. "date" => null,
  394. "duration" => null,
  395. "url" =>
  396. "https://open.spotify.com/show/" .
  397. explode(
  398. ":",
  399. $podcast["uri"],
  400. 3
  401. )[2],
  402. "stream" => [
  403. "endpoint" => null,
  404. "url" => null
  405. ]
  406. ];
  407. }
  408. // get audio books (put in podcasts)
  409. foreach($payload["data"]["searchV2"]["audiobooks"]["items"] as $podcast){
  410. if(isset($podcast["data"])){
  411. $podcast = $podcast["data"];
  412. }
  413. $description = [];
  414. foreach($podcast["topics"]["items"] as $subject){
  415. $description[] = $subject["title"];
  416. }
  417. $description = implode(", ", $description);
  418. if($description == ""){
  419. $description = null;
  420. }
  421. $authors = [];
  422. foreach($podcast["authors"] as $author){
  423. $authors[] = $author["name"];
  424. }
  425. $authors = implode(", ", $authors);
  426. if($authors == ""){
  427. $authors = null;
  428. }
  429. $uri =
  430. explode(
  431. ":",
  432. $podcast["uri"],
  433. 3
  434. )[2];
  435. $out["podcast"][] = [
  436. "title" => $podcast["name"],
  437. "description" => $description,
  438. "author" => [
  439. "name" => $authors,
  440. "url" => null,
  441. "avatar" => null
  442. ],
  443. "thumb" => $this->get_thumb($podcast["coverArt"]),
  444. "date" => strtotime($podcast["publishDate"]["isoString"]),
  445. "duration" => null,
  446. "url" => "https://open.spotify.com/show/" . $uri,
  447. "stream" => [
  448. "endpoint" => "spotify",
  449. "url" => "episode." . $uri
  450. ]
  451. ];
  452. }
  453. // get episodes (and place them in podcasts)
  454. foreach($payload["data"]["searchV2"]["episodes"]["items"] as $podcast){
  455. if(isset($podcast["data"])){
  456. $podcast = $podcast["data"];
  457. }
  458. $out["podcast"][] = [
  459. "title" => $podcast["name"],
  460. "description" => $this->limitstrlen($podcast["description"]),
  461. "author" => [
  462. "name" =>
  463. isset(
  464. $podcast["podcastV2"]["data"]["publisher"]["name"]
  465. ) ?
  466. $podcast["podcastV2"]["data"]["publisher"]["name"]
  467. : null,
  468. "url" => null,
  469. "avatar" => null
  470. ],
  471. "thumb" => $this->get_thumb($podcast["coverArt"]),
  472. "date" => strtotime($podcast["releaseDate"]["isoString"]),
  473. "duration" => $podcast["duration"]["totalMilliseconds"] / 1000,
  474. "url" =>
  475. "https://open.spotify.com/show/" .
  476. explode(
  477. ":",
  478. $podcast["uri"],
  479. 3
  480. )[2],
  481. "stream" => [
  482. "endpoint" => null,
  483. "url" => null
  484. ]
  485. ];
  486. }
  487. // get authors
  488. foreach($payload["data"]["searchV2"]["artists"]["items"] as $user){
  489. if(isset($user["data"])){
  490. $user = $user["data"];
  491. }
  492. $avatar = $this->get_thumb($user["visuals"]["avatarImage"]);
  493. $out["author"][] = [
  494. "title" =>
  495. (
  496. $user["profile"]["verified"] === true ?
  497. "✓ " : ""
  498. ) .
  499. $user["profile"]["name"],
  500. "followers" => null,
  501. "description" => null,
  502. "thumb" => $avatar,
  503. "url" =>
  504. "https://open.spotify.com/artist/" .
  505. explode(
  506. ":",
  507. $user["uri"],
  508. 3
  509. )[2]
  510. ];
  511. }
  512. // get users
  513. foreach($payload["data"]["searchV2"]["users"]["items"] as $user){
  514. if(isset($user["data"])){
  515. $user = $user["data"];
  516. }
  517. $avatar = $this->get_thumb($user["avatar"]);
  518. $out["user"][] = [
  519. "title" => $user["displayName"] . " (@{$user["id"]})",
  520. "followers" => null,
  521. "description" => null,
  522. "thumb" => $avatar,
  523. "url" => "https://open.spotify.com/user/" . $user["id"]
  524. ];
  525. }
  526. return $out;
  527. }
  528. private function get_artists($artists){
  529. $artist_out = [];
  530. foreach($artists["items"] as $artist){
  531. $artist_out[] = $artist["profile"]["name"];
  532. }
  533. $artist_out =
  534. implode(", ", $artist_out);
  535. if($artist_out == ""){
  536. return [null, null];
  537. }
  538. $artist_link =
  539. $artist === null ?
  540. null :
  541. "https://open.spotify.com/artist/" .
  542. explode(
  543. ":",
  544. $artists["items"][0]["uri"]
  545. )[2];
  546. return [$artist_out, $artist_link];
  547. }
  548. private function get_thumb($cover){
  549. $thumb_out = null;
  550. if($cover !== null){
  551. foreach($cover["sources"] as $thumb){
  552. if(
  553. $thumb_out === null ||
  554. (int)$thumb["width"] > $thumb_out["width"]
  555. ){
  556. $thumb_out = $thumb;
  557. }
  558. }
  559. }
  560. if($thumb_out === null){
  561. return [
  562. "url" => null,
  563. "ratio" => null
  564. ];
  565. }else{
  566. return [
  567. "url" => $thumb_out["url"],
  568. "ratio" => "1:1"
  569. ];
  570. }
  571. }
  572. private function limitstrlen($text){
  573. return
  574. explode(
  575. "\n",
  576. wordwrap(
  577. str_replace(
  578. ["\n\r", "\r\n", "\n", "\r"],
  579. " ",
  580. $text
  581. ),
  582. 300,
  583. "\n"
  584. ),
  585. 2
  586. )[0];
  587. }
  588. }