yt.php 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316
  1. <?php
  2. class yt{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("yt");
  6. }
  7. public function getfilters($page){
  8. if($page != "videos"){
  9. return [];
  10. }
  11. return [
  12. "date" => [
  13. "display" => "Time posted",
  14. "option" => [
  15. "any" => "Any time",
  16. "hour" => "Last hour",
  17. "today" => "Today",
  18. "week" => "This week",
  19. "month" => "This month",
  20. "year" => "This year"
  21. ]
  22. ],
  23. "type" => [
  24. "display" => "Type",
  25. "option" => [
  26. "video" => "Video",
  27. "channel" => "Channel",
  28. "playlist" => "Playlist",
  29. "Movie" => "Movie"
  30. ]
  31. ],
  32. "duration" => [
  33. "display" => "Duration",
  34. "option" => [
  35. "any" => "Any duration",
  36. "short" => "Short (>4min)",
  37. "medium" => "Medium (4-20min)",
  38. "long" => "Long (<20min)"
  39. ]
  40. ],
  41. "feature" => [
  42. "display" => "Feature",
  43. "option" => [
  44. "any" => "No features",
  45. "live" => "Live",
  46. "4k" => "4K",
  47. "hd" => "HD",
  48. "subtitles" => "Subtitles/CC",
  49. "creativecommons" => "Creative Commons",
  50. "360" => "VR 360°",
  51. "vr180" => "VR 180°",
  52. "3d" => "3D",
  53. "hdr" => "HDR"
  54. ]
  55. ],
  56. "sort" => [
  57. "display" => "Sort by",
  58. "option" => [
  59. "relevance" => "Relevance",
  60. "upload_date" => "Upload date",
  61. "view_count" => "View count",
  62. "rating" => "Rating"
  63. ]
  64. ]
  65. ];
  66. }
  67. private function ytfilter($date, $type, $duration, $feature, $sort){
  68. // ------------
  69. // INCOMPATIBLE FILTERS
  70. // channel,playlist DURATION, FEATURES, SORT BY
  71. // Movie Features=[live, subtitles, creative commons, 3d]
  72. // live, 3D
  73. // Type[channel, playlist, movie]
  74. // UPLOAD DATE, DURATION, 4k, 360, VR180, HDR
  75. // Type[channel, playlist]
  76. // -----------
  77. // MUST BE TOGETHER
  78. // Relevance,upload date Type=Video
  79. switch($type){
  80. case "channel":
  81. case "playlist":
  82. if($duration != "any"){ $duration = "any"; }
  83. if($feature != "any"){ $feature = "any"; }
  84. if($sort != "any"){ $sort = "any"; }
  85. break;
  86. case "movie":
  87. if(
  88. in_array(
  89. $feature,
  90. [
  91. "live",
  92. "subtitles",
  93. "creative_commons",
  94. "3d"
  95. ],
  96. )
  97. ){
  98. $feature = "any";
  99. }
  100. break;
  101. }
  102. switch($feature){
  103. case "live":
  104. case "3d":
  105. if(
  106. in_array(
  107. $type,
  108. [
  109. "channel",
  110. "playlist",
  111. "movie"
  112. ],
  113. )
  114. ){
  115. $type = "video";
  116. }
  117. break;
  118. }
  119. if(
  120. (
  121. $date != "any" ||
  122. $duration != "any" ||
  123. $feature == "4k" ||
  124. $feature == "360" ||
  125. $feature == "vr180" ||
  126. $feature == "hdr"
  127. ) &&
  128. (
  129. $type == "channel" ||
  130. $type == "playlist"
  131. )
  132. ){
  133. $type = "video";
  134. }
  135. if(
  136. $date == "any" &&
  137. $type == "video" &&
  138. $duration == "any" &&
  139. $feature == "any" &&
  140. $sort == "relevance"
  141. ){
  142. return null;
  143. }
  144. //print_r([$date, $type, $duration, $feature, $sort]);
  145. /*
  146. Encode hex data
  147. */
  148. // UPLOAD DATE
  149. // hour EgQIARAB 12 04 08 01 10 01
  150. // today EgQIAhAB 12 04 08 02 10 01
  151. // week EgQIAxAB 12 04 08 03 10 01
  152. // month EgQIBBAB 12 04 08 04 10 01
  153. // year EgQIBRAB 12 04 08 05 10 01
  154. // TYPE
  155. // video EgIQAQ%253D%253D 12 02 10 01
  156. // channel EgIQAg%253D%253D 12 02 10 02
  157. // playlist EgIQAw%253D%253D 12 02 10 03
  158. // movie EgIQBA%253D%253D 12 02 10 04
  159. // DURATION
  160. // -4min EgIYAQ%253D%253D 12 02 18 01
  161. // 4-20min EgIYAw%253D%253D 12 02 18 03
  162. // 20+min EgIYAg%253D%253D 12 02 18 02
  163. // FEATURE
  164. // live EgJAAQ%253D%253D 12 02 40 01
  165. // 4K EgJwAQ%253D%253D 12 02 70 01
  166. // HD EgIgAQ%253D%253D 12 02 20 01
  167. // Subtitles/CC EgIoAQ%253D%253D 12 02 28 01
  168. // Creative Commons EgIwAQ%253D%253D 12 02 30 01
  169. // 360 EgJ4AQ%253D%253D 12 02 78 01
  170. // VR180 EgPQAQE%253D 12 03 d0 01 01
  171. // 3D EgI4AQ%253D%253D 12 02 38 01
  172. // HDR EgPIAQE%253D 12 03 c8 01 01
  173. // (location & purchased unused)
  174. // SORT BY
  175. // Relevance CAASAhAB 08 00 12 02 10 01 (is nothing by default)
  176. // Upload date CAI%253D 08 02
  177. // View count CAM%253D 08 03
  178. // Rating CAE%253D 08 01
  179. // video
  180. // 12 02 10 01
  181. // under 4 minutes
  182. // 12 02 18 01
  183. // video + under 4 minutes
  184. // 12 04 10 01 18 01
  185. // video + under 4 minutes + HD
  186. // 08 00 12 06 10 01 18 01 20 01
  187. // video + under 4 minutes + upload date
  188. // 08 02 12 04 10 01 18 01
  189. // video + under 4 minutes + HD + upload date
  190. // 08 02 12 06 10 01 18 01 20 01
  191. // this year + video + under 4 minutes + HD + upload date
  192. // 08 02 12 08 08 05 10 01 18 01 20 01
  193. // this week + video + over 20 minutes + HD + view count
  194. // 08 03 12 08 08 03 10 01 18 02 20 01
  195. //echo urlencode(urlencode(base64_encode(hex2bin($str))));
  196. //echo bin2hex(base64_decode(urldecode(urldecode("CAI%253D"))));
  197. // week + video + 20min + rating
  198. // 08 01 12 06 08 03 10 01 18 02
  199. // week + video + 20min + live + rating
  200. // 08 01 12 08 08 03 10 01 18 02 40 01
  201. // live 12 02 40 01
  202. $hex = null;
  203. if(
  204. $date == "any" &&
  205. $type == "video" &&
  206. $duration == "any" &&
  207. $feature == "any" &&
  208. $sort == "relevance"
  209. ){
  210. return $hex;
  211. }
  212. $opcode = 0;
  213. if($date != "any"){ $opcode += 2; }
  214. if($type != "any"){ $opcode += 2; }
  215. if($duration != "any"){ $opcode += 2; }
  216. switch($feature){
  217. case "live":
  218. case "4k":
  219. case "hd":
  220. case "subtitles":
  221. case "creativecommons":
  222. case "360":
  223. case "3d":
  224. $opcode += 2;
  225. break;
  226. case "hdr":
  227. case "vr180":
  228. $opcode += 3;
  229. break;
  230. }
  231. switch($sort){
  232. case "relevance": $hex .= "0800"; break;
  233. case "upload_date": $hex .= "0802"; break;
  234. case "view_count": $hex .= "0803"; break;
  235. case "rating": $hex .= "0801"; break;
  236. }
  237. $hex .= "12" . "0".$opcode;
  238. switch($date){
  239. case "hour": $hex .= "0801"; break;
  240. case "today": $hex .= "0802"; break;
  241. case "week": $hex .= "0803"; break;
  242. case "month": $hex .= "0804"; break;
  243. case "year": $hex .= "0805"; break;
  244. }
  245. switch($type){
  246. case "video": $hex .= "1001"; break;
  247. case "channel": $hex .= "1002"; break;
  248. case "playlist": $hex .= "1003"; break;
  249. case "movie": $hex .= "1004"; break;
  250. }
  251. switch($duration){
  252. case "short": $hex .= "1801"; break;
  253. case "medium": $hex .= "1803"; break;
  254. case "long": $hex .= "1802"; break;
  255. }
  256. switch($feature){
  257. case "live": $hex .= "4001"; break;
  258. case "4k": $hex .= "7001"; break;
  259. case "hd": $hex .= "2001"; break;
  260. case "subtitles": $hex .= "2801"; break;
  261. case "creativecommons": $hex .= "3001"; break;
  262. case "360": $hex .= "7801"; break;
  263. case "vr180": $hex .= "d00101"; break;
  264. case "3d": $hex .= "3801"; break;
  265. case "hdr": $hex .= "c80101"; break;
  266. }
  267. //echo $hex . "\n\n";
  268. return urlencode(base64_encode(hex2bin($hex)));
  269. }
  270. // me reading youtube's json
  271. // https://imgur.com/X9hVlFX
  272. const req_web = 0;
  273. const req_xhr = 1;
  274. private function get($proxy, $url, $get = [], $reqtype = self::req_web, $continuation = null){
  275. $curlproc = curl_init();
  276. if($get !== []){
  277. $get = http_build_query($get);
  278. $url .= "?" . $get;
  279. }
  280. curl_setopt($curlproc, CURLOPT_URL, $url);
  281. switch($reqtype){
  282. case self::req_web:
  283. $headers =
  284. ["User-Agent: " . config::USER_AGENT,
  285. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  286. "Accept-Language: en-US,en;q=0.5",
  287. "Accept-Encoding: gzip, deflate, br, zstd",
  288. "Cookie: PREF=tz=America.New_York",
  289. "DNT: 1",
  290. "Connection: keep-alive",
  291. "Upgrade-Insecure-Requests: 1",
  292. "Sec-Fetch-Dest: document",
  293. "Sec-Fetch-Mode: navigate",
  294. "Sec-Fetch-Site: none",
  295. "Sec-Fetch-User: ?1"];
  296. break;
  297. case self::req_xhr:
  298. $headers =
  299. ["User-Agent: " . config::USER_AGENT,
  300. "Accept: */*",
  301. "Accept-Language: en-US,en;q=0.5",
  302. "Accept-Encoding: gzip, deflate, br, zstd",
  303. "Cookie: PREF=tz=America.New_York",
  304. "Referer: https://youtube.com.com/",
  305. "Content-Type: application/json",
  306. "Content-Length: " . strlen($continuation),
  307. "DNT: 1",
  308. "Connection: keep-alive",
  309. "Sec-Fetch-Dest: empty",
  310. "Sec-Fetch-Mode: same-origin",
  311. "Sec-Fetch-Site: same-origin"];
  312. curl_setopt($curlproc, CURLOPT_POST, true);
  313. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $continuation);
  314. break;
  315. }
  316. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  317. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  318. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  319. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  320. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  321. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  322. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  323. $this->backend->assign_proxy($curlproc, $proxy);
  324. $data = curl_exec($curlproc);
  325. if(curl_errno($curlproc)){
  326. throw new Exception(curl_error($curlproc));
  327. }
  328. curl_close($curlproc);
  329. return $data;
  330. }
  331. public function video($get){
  332. $out = [
  333. "status" => "ok",
  334. "npt" => null,
  335. "video" => [],
  336. "author" => [],
  337. "livestream" => [],
  338. "playlist" => [],
  339. "reel" => []
  340. ];
  341. if($get["npt"]){
  342. $first_page = false;
  343. [$npt, $proxy] =
  344. $this->backend->get(
  345. $get["npt"],
  346. "videos"
  347. );
  348. $npt = json_decode($npt, true);
  349. try{
  350. $json = $this->get(
  351. $proxy,
  352. "https://www.youtube.com/youtubei/v1/search",
  353. [
  354. "key" => $npt["key"],
  355. "prettyPrint" => "false"
  356. ],
  357. self::req_xhr,
  358. json_encode($npt["post"])
  359. );
  360. }catch(Exception $error){
  361. throw new Exception("Could not fetch search page");
  362. }
  363. $json = json_decode($json, true);
  364. }else{
  365. $search = $get["s"];
  366. if(strlen($search) === 0){
  367. throw new Exception("Search term is empty!");
  368. }
  369. $proxy = $this->backend->get_ip();
  370. $date = $get["date"];
  371. $type = $get["type"];
  372. $duration = $get["duration"];
  373. $feature = $get["feature"];
  374. $sort = $get["sort"];
  375. // parse ytInitialData
  376. $get = [
  377. "search_query" => $search
  378. ];
  379. if(
  380. (
  381. $filter =
  382. $this->ytfilter(
  383. $date,
  384. $type,
  385. $duration,
  386. $feature,
  387. $sort
  388. )
  389. ) !== null
  390. ){
  391. $get["sp"] = $filter;
  392. }
  393. try{
  394. $json = $this->get(
  395. $proxy,
  396. "https://www.youtube.com/results",
  397. $get
  398. );
  399. }catch(Exception $error){
  400. throw new Exception("Could not fetch search page");
  401. }
  402. //$json = file_get_contents("scraper/yt.html");
  403. // get next page data, unique to first page
  404. if(
  405. !preg_match(
  406. '/ytcfg\.set\(({".*})\); *window\.ytcfg/',
  407. $json,
  408. $ytconfig
  409. )
  410. ){
  411. throw new Exception("Could not get ytcfg");
  412. }
  413. $ytconfig = json_decode($ytconfig[1], true);
  414. $first_page = true;
  415. // this is where all of the data is
  416. if(
  417. !preg_match(
  418. '/ytInitialData *= *({.*});<\/script>/',
  419. $json,
  420. $json
  421. )
  422. ){
  423. throw new Exception("Could not get ytInitialData");
  424. }
  425. $json = json_decode($json[1], true);
  426. }
  427. $items = [
  428. "channelRenderer" => [],
  429. "videoRenderer" => [],
  430. "shortsLockupViewModel" => [],
  431. "lockupViewModel" => [], // playlists
  432. "continuationItemRenderer" => [] // pagination
  433. ];
  434. $search = [
  435. "channelRenderer",
  436. "videoRenderer",
  437. "shortsLockupViewModel",
  438. "lockupViewModel",
  439. "continuationItemRenderer"
  440. ];
  441. $this->recursive_search($search, $items, $json);
  442. // add normal videos
  443. foreach($items["videoRenderer"] as $video){
  444. $show_live_badge = false;
  445. if(
  446. $video["navigationEndpoint"]
  447. ["commandMetadata"]
  448. ["webCommandMetadata"]
  449. ["webPageType"]
  450. == "WEB_PAGE_TYPE_SHORTS"
  451. ){
  452. $type = "reel";
  453. }elseif(isset($video["upcomingEventData"])){
  454. // is an upcoming event, categorize as livestream
  455. $type = "livestream";
  456. }else{
  457. $type = "video";
  458. // detect if its a livestream
  459. if(isset($video["badges"])){
  460. foreach($video["badges"] as $badge){
  461. if(
  462. $badge
  463. ["metadataBadgeRenderer"]
  464. ["label"] == "LIVE"
  465. ){
  466. $type = "livestream";
  467. $show_live_badge = true;
  468. break;
  469. }
  470. }
  471. }
  472. }
  473. $out[$type][] = [
  474. "title" =>
  475. $this->assemble_runs(
  476. $video
  477. ["title"]
  478. ),
  479. "description" =>
  480. isset(
  481. $video["detailedMetadataSnippets"]
  482. [0]
  483. ["snippetText"]
  484. ) ?
  485. $this->titledots(
  486. $this->assemble_runs(
  487. $video["detailedMetadataSnippets"]
  488. [0]
  489. ["snippetText"]
  490. )
  491. ) : null,
  492. "author" => [
  493. "name" =>
  494. $this->assemble_runs(
  495. $video["longBylineText"]
  496. ),
  497. "url" =>
  498. "https://www.youtube.com" .
  499. $video["ownerText"]
  500. ["runs"]
  501. [0]
  502. ["navigationEndpoint"]
  503. ["commandMetadata"]
  504. ["webCommandMetadata"]
  505. ["url"],
  506. "avatar" =>
  507. $video["channelThumbnailSupportedRenderers"]
  508. ["channelThumbnailWithLinkRenderer"]
  509. ["thumbnail"]
  510. ["thumbnails"]
  511. [0]
  512. ["url"]
  513. ],
  514. "date" =>
  515. isset(
  516. $video["publishedTimeText"]
  517. ["simpleText"]
  518. ) ?
  519. strtotime(
  520. trim(
  521. str_replace(
  522. "Streamed", "",
  523. $video["publishedTimeText"]
  524. ["simpleText"]
  525. )
  526. )
  527. ) : null,
  528. "duration" =>
  529. $show_live_badge === true ?
  530. "_LIVE" :
  531. (
  532. isset(
  533. $video["lengthText"]
  534. ["simpleText"]
  535. ) ?
  536. $this->hms2int(
  537. $video["lengthText"]
  538. ["simpleText"]
  539. ) : null
  540. ),
  541. "views" =>
  542. isset(
  543. $video["viewCountText"]
  544. ["simpleText"]
  545. ) ?
  546. $this->views2int(
  547. $video["viewCountText"]
  548. ["simpleText"]
  549. ) : null,
  550. "thumb" => [
  551. "ratio" => "16:9",
  552. "url" =>
  553. $video["thumbnail"]
  554. ["thumbnails"]
  555. [0]
  556. ["url"]
  557. ],
  558. "url" => "https://www.youtube.com/watch?v=" . $video["videoId"]
  559. ];
  560. }
  561. // add channels
  562. foreach($items["channelRenderer"] as $channel){
  563. $out["author"][] = [
  564. "title" =>
  565. $channel
  566. ["title"]
  567. ["simpleText"],
  568. "followers" =>
  569. isset(
  570. $channel
  571. ["videoCountText"]
  572. ["simpleText"]
  573. ) ?
  574. $this->truncatedcount2int(
  575. $channel
  576. ["videoCountText"]
  577. ["simpleText"]
  578. ) :
  579. null,
  580. "description" =>
  581. isset($channel["descriptionSnippet"]) ?
  582. $this->titledots(
  583. $this->assemble_runs(
  584. $channel
  585. ["descriptionSnippet"]
  586. )
  587. ) : null,
  588. "thumb" =>
  589. [
  590. "url" =>
  591. $this->checkhttpspresence(
  592. $channel
  593. ["thumbnail"]
  594. ["thumbnails"]
  595. [
  596. count(
  597. $channel
  598. ["thumbnail"]
  599. ["thumbnails"]
  600. ) - 1
  601. ]
  602. ["url"]
  603. ),
  604. "ratio" => "1:1"
  605. ],
  606. "url" =>
  607. "https://www.youtube.com/channel/" .
  608. $channel
  609. ["channelId"]
  610. ];
  611. }
  612. // add reels
  613. foreach($items["shortsLockupViewModel"] as $reel){
  614. $out["reel"][] = [
  615. "title" =>
  616. $reel
  617. ["overlayMetadata"]
  618. ["primaryText"]
  619. ["content"],
  620. "description" =>
  621. isset(
  622. $reel
  623. ["overlayMetadata"]
  624. ["primaryText"]
  625. ["content"]
  626. ) ?
  627. $this->titledots(
  628. $reel
  629. ["overlayMetadata"]
  630. ["primaryText"]
  631. ["content"]
  632. ) : null,
  633. "author" => [
  634. "name" => null,
  635. "url" => null,
  636. "avatar" => null
  637. ],
  638. "date" => null,
  639. "duration" => null,
  640. "views" =>
  641. $this->truncatedcount2int(
  642. $reel
  643. ["overlayMetadata"]
  644. ["secondaryText"]
  645. ["content"]
  646. ),
  647. "thumb" => [
  648. "url" =>
  649. $reel
  650. ["thumbnailViewModel"]
  651. ["thumbnailViewModel"]
  652. ["image"]
  653. ["sources"]
  654. [0]
  655. ["url"],
  656. "ratio" => "9:16"
  657. ],
  658. "url" =>
  659. "https://www.youtube.com/watch?v=" .
  660. $reel
  661. ["onTap"]
  662. ["innertubeCommand"]
  663. ["reelWatchEndpoint"]
  664. ["videoId"]
  665. ];
  666. }
  667. // add playlists
  668. foreach($items["lockupViewModel"] as $playlist){
  669. // get author of playlist
  670. $author_name = null;
  671. $author_url = null;
  672. if(
  673. !isset(
  674. $playlist
  675. ["metadata"]
  676. ["lockupMetadataViewModel"]
  677. ["title"]
  678. ["content"]
  679. ) ||
  680. !isset(
  681. $playlist
  682. ["itemPlayback"]
  683. ["inlinePlayerData"]
  684. ["onSelect"]
  685. ["innertubeCommand"]
  686. ["commandMetadata"]
  687. ["webCommandMetadata"]
  688. ["url"]
  689. )
  690. ){
  691. // got an ad viewmodel or some invalid item with no link
  692. // i dont know why the fuck that happens dont ask me
  693. continue;
  694. }
  695. foreach(
  696. $playlist
  697. ["metadata"]
  698. ["lockupMetadataViewModel"]
  699. ["metadata"]
  700. ["contentMetadataViewModel"]
  701. ["metadataRows"]
  702. [0]
  703. ["metadataParts"]
  704. as $possible_author
  705. ){
  706. if(
  707. isset(
  708. $possible_author
  709. ["text"]
  710. ["commandRuns"]
  711. [0]
  712. ["onTap"]
  713. ["innertubeCommand"]
  714. ["commandMetadata"]
  715. ["webCommandMetadata"]
  716. ["webPageType"]
  717. ) &&
  718. $possible_author
  719. ["text"]
  720. ["commandRuns"]
  721. [0]
  722. ["onTap"]
  723. ["innertubeCommand"]
  724. ["commandMetadata"]
  725. ["webCommandMetadata"]
  726. ["webPageType"] == "WEB_PAGE_TYPE_CHANNEL"
  727. ){
  728. $author_name = $possible_author["text"]["content"];
  729. $author_url =
  730. "https://www.youtube.com" .
  731. $possible_author
  732. ["text"]
  733. ["commandRuns"]
  734. [0]
  735. ["onTap"]
  736. ["innertubeCommand"]
  737. ["commandMetadata"]
  738. ["webCommandMetadata"]
  739. ["url"];
  740. break;
  741. }
  742. }
  743. $out["playlist"][] = [
  744. "title" =>
  745. $playlist
  746. ["metadata"]
  747. ["lockupMetadataViewModel"]
  748. ["title"]
  749. ["content"],
  750. "description" => null,
  751. "author" => [
  752. "name" => $author_name,
  753. "url" => $author_url,
  754. "avatar" => null
  755. ],
  756. "date" => null,
  757. "duration" => null,
  758. "views" => null,
  759. "thumb" => [
  760. "url" =>
  761. $playlist
  762. ["contentImage"]
  763. ["collectionThumbnailViewModel"]
  764. ["primaryThumbnail"]
  765. ["thumbnailViewModel"]
  766. ["image"]
  767. ["sources"]
  768. [0]
  769. ["url"],
  770. "ratio" => "16:9"
  771. ],
  772. "url" =>
  773. "https://www.youtube.com" .
  774. $playlist
  775. ["itemPlayback"]
  776. ["inlinePlayerData"]
  777. ["onSelect"]
  778. ["innertubeCommand"]
  779. ["commandMetadata"]
  780. ["webCommandMetadata"]
  781. ["url"]
  782. ];
  783. }
  784. // get next page
  785. $found_npt = false;
  786. if($first_page === true){
  787. // we're using the old pagination trick, google now sends encrypted data
  788. if(count($items["continuationItemRenderer"]) !== 0){
  789. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenWidthPoints"] = 1920;
  790. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenHeightPoints"] = 999;
  791. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenPixelDensity"] = 1;
  792. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenDensityFloat"] = 1;
  793. $ytconfig["INNERTUBE_CONTEXT"]["client"]["utcOffsetMinutes"] = -240;
  794. $ytconfig["INNERTUBE_CONTEXT"]["client"]["internalExperimentFlags"] = [];
  795. $ytconfig["INNERTUBE_CONTEXT"]["client"]["concistencyTokenJars"] = [];
  796. $ytconfig["INNERTUBE_CONTEXT"]["client"]["mainAppWebInfo"] = [
  797. "graftUrl" => $ytconfig["INNERTUBE_CONTEXT"]["client"]["originalUrl"],
  798. "webDisplayMode" => "WEB_DISPLAY_MODE_BROWSER",
  799. "isWebNativeShareAvailable" => false
  800. ];
  801. $ytconfig["INNERTUBE_CONTEXT"]["adSignalsInfo"] = [
  802. "params" => [
  803. [
  804. "key" => "dt",
  805. "value" => (string)$ytconfig["TIME_CREATED_MS"]
  806. ],
  807. [
  808. "key" => "flash",
  809. "value" => "0"
  810. ],
  811. [
  812. "key" => "frm",
  813. "value" => "0"
  814. ],
  815. [
  816. "key" => "u_tz",
  817. "value" => "-240"
  818. ],
  819. [
  820. "key" => "u_his",
  821. "value" => "3"
  822. ],
  823. [
  824. "key" => "u_h",
  825. "value" => "1080"
  826. ],
  827. [
  828. "key" => "u_w",
  829. "value" => "1920"
  830. ],
  831. [
  832. "key" => "u_ah",
  833. "value" => "1080"
  834. ],
  835. [
  836. "key" => "u_cd",
  837. "value" => "24"
  838. ],
  839. [
  840. "key" => "bc",
  841. "value" => "31"
  842. ],
  843. [
  844. "key" => "bih",
  845. "value" => "999"
  846. ],
  847. [
  848. "key" => "biw",
  849. "value" => "1920"
  850. ],
  851. [
  852. "key" => "brdim",
  853. "value" => "0,0,0,0,1920,0,1920,1061,1920,999"
  854. ],
  855. [
  856. "key" => "vis",
  857. "value" => "1"
  858. ],
  859. [
  860. "key" => "wgl",
  861. "value" => "true"
  862. ],
  863. [
  864. "key" => "ca_type",
  865. "value" => "image"
  866. ]
  867. ]
  868. ];
  869. $found_npt = true;
  870. $npt = [
  871. "key" =>
  872. $ytconfig
  873. ["INNERTUBE_API_KEY"],
  874. "post" => [
  875. "context" =>
  876. $ytconfig
  877. ["INNERTUBE_CONTEXT"],
  878. "continuation" =>
  879. $items
  880. ["continuationItemRenderer"]
  881. [0]
  882. ["continuationEndpoint"]
  883. ["continuationCommand"]
  884. ["token"]
  885. ]
  886. ];
  887. }
  888. }else{
  889. // handle next page for json endpoint
  890. if(count($items["continuationItemRenderer"]) !== 0){
  891. $found_npt = true;
  892. $npt["post"]["continuation"] =
  893. $items
  894. ["continuationItemRenderer"]
  895. [0]
  896. ["continuationEndpoint"]
  897. ["continuationCommand"]
  898. ["token"];
  899. }
  900. }
  901. if($found_npt){
  902. $out["npt"] =
  903. $this->backend->store(
  904. json_encode(
  905. $npt
  906. ),
  907. "videos",
  908. $proxy
  909. );
  910. }
  911. return $out;
  912. }
  913. private function recursive_search(&$search, &$items, &$pointer){
  914. foreach($pointer as $key => $value){
  915. if(
  916. in_array(
  917. $key,
  918. $search
  919. )
  920. ){
  921. // found interesting item
  922. $items[$key][] = $value;
  923. }elseif(is_array($value)){
  924. $this->recursive_search($search, $items, $value);
  925. }
  926. }
  927. }
  928. private function textualdate2unix($number){
  929. $number =
  930. explode(
  931. " ",
  932. str_replace(
  933. [
  934. " ago",
  935. "seconds",
  936. "minutes",
  937. "hours",
  938. "days",
  939. "weeks",
  940. "months",
  941. "years"
  942. ],
  943. [
  944. "",
  945. "second",
  946. "minute",
  947. "hour",
  948. "day",
  949. "week",
  950. "month",
  951. "year"
  952. ],
  953. $number
  954. ),
  955. 2
  956. );
  957. $time = 0;
  958. switch($number[1]){
  959. case "second":
  960. $time = (int)$number[0];
  961. break;
  962. case "minute":
  963. $time = (int)$number[0] * 60;
  964. break;
  965. case "hour":
  966. $time = (int)$number[0] * 3600;
  967. break;
  968. case "day":
  969. $time = (int)$number[0] * 86400;
  970. break;
  971. case "week":
  972. $time = (int)$number[0] * 604800;
  973. break;
  974. case "month":
  975. $time = (int)$number[0] * 2629746;
  976. break;
  977. case "year":
  978. $time = (int)$number[0] * 31556952;
  979. break;
  980. }
  981. return time() - $time;
  982. }
  983. private function checkhttpspresence($link){
  984. if(substr($link, 0, 2) == "//"){
  985. return "https:" . $link;
  986. }
  987. return $link;
  988. }
  989. private function textualtime2int($number){
  990. $number = explode(" - ", $number);
  991. if(count($number) >= 2){
  992. $number = $number[count($number) - 2];
  993. }else{
  994. $number = $number[0];
  995. }
  996. $number =
  997. str_replace(
  998. [
  999. " ",
  1000. "seconds",
  1001. "minutes",
  1002. "hours",
  1003. ],
  1004. [
  1005. "",
  1006. "second",
  1007. "minute",
  1008. "hour"
  1009. ],
  1010. $number
  1011. );
  1012. preg_match_all(
  1013. '/([0-9]+)(second|minute|hour)/',
  1014. $number,
  1015. $number
  1016. );
  1017. $time = 0;
  1018. for($i=0; $i<count($number[0]); $i++){
  1019. switch($number[2][$i]){
  1020. case "second":
  1021. $time = $time + (int)$number[1][$i];
  1022. break;
  1023. case "minute":
  1024. $time = $time + ((int)$number[1][$i] * 60);
  1025. break;
  1026. case "hour":
  1027. $time = $time + ((int)$number[1][$i] * 3600);
  1028. break;
  1029. }
  1030. }
  1031. return $time;
  1032. }
  1033. private function views2int($views){
  1034. return
  1035. (int)str_replace(
  1036. ",", "",
  1037. explode(" ", $views, 2)[0]
  1038. );
  1039. }
  1040. private function hms2int($time){
  1041. $parts = explode(":", $time, 3);
  1042. $time = 0;
  1043. if(count($parts) === 3){
  1044. // hours
  1045. $time = $time + ((int)$parts[0] * 3600);
  1046. array_shift($parts);
  1047. }
  1048. if(count($parts) === 2){
  1049. // minutes
  1050. $time = $time + ((int)$parts[0] * 60);
  1051. array_shift($parts);
  1052. }
  1053. // seconds
  1054. $time = $time + (int)$parts[0];
  1055. return $time;
  1056. }
  1057. private function truncatedcount2int($number){
  1058. // decimal should always be 1 number long
  1059. $number = explode(" ", $number, 2);
  1060. $number = $number[0];
  1061. $unit = strtolower($number[strlen($number) - 1]);
  1062. $tmp = explode(".", $number, 2);
  1063. $number = (int)$number;
  1064. if(count($tmp) === 2){
  1065. $decimal = (int)$tmp[1];
  1066. }else{
  1067. $decimal = 0;
  1068. }
  1069. switch($unit){
  1070. case "k":
  1071. $exponant = 1000;
  1072. break;
  1073. case "m":
  1074. $exponant = 1000000;
  1075. break;
  1076. case "b";
  1077. $exponant = 1000000000;
  1078. break;
  1079. default:
  1080. $exponant = 1;
  1081. break;
  1082. }
  1083. return ($number * $exponant) + ($decimal * ($exponant / 10));
  1084. }
  1085. private function titledots($title){
  1086. $substr = substr($title, -3);
  1087. if(
  1088. $substr == "..." ||
  1089. $substr == "…"
  1090. ){
  1091. return str_replace(["\r", "\n"], " ", trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0"));
  1092. }
  1093. return str_replace(["\r", "\n"], " ", trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0"));
  1094. }
  1095. private function assemble_runs($runs){
  1096. if(!isset($runs["runs"])){ return ""; }
  1097. $text = "";
  1098. foreach($runs["runs"] as $run){
  1099. if(isset($run["text"])){
  1100. $text .= $run["text"];
  1101. }
  1102. }
  1103. return $text;
  1104. }
  1105. }