yt.php 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  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" => "https://i.ytimg.com/vi/" . $video["videoId"] . "/hq720.jpg"
  553. ],
  554. "url" => "https://www.youtube.com/watch?v=" . $video["videoId"]
  555. ];
  556. }
  557. // add channels
  558. foreach($items["channelRenderer"] as $channel){
  559. $out["author"][] = [
  560. "title" =>
  561. $channel
  562. ["title"]
  563. ["simpleText"],
  564. "followers" =>
  565. isset(
  566. $channel
  567. ["videoCountText"]
  568. ["simpleText"]
  569. ) ?
  570. $this->truncatedcount2int(
  571. $channel
  572. ["videoCountText"]
  573. ["simpleText"]
  574. ) :
  575. null,
  576. "description" =>
  577. isset($channel["descriptionSnippet"]) ?
  578. $this->titledots(
  579. $this->assemble_runs(
  580. $channel
  581. ["descriptionSnippet"]
  582. )
  583. ) : null,
  584. "thumb" =>
  585. [
  586. "url" =>
  587. $this->checkhttpspresence(
  588. $channel
  589. ["thumbnail"]
  590. ["thumbnails"]
  591. [
  592. count(
  593. $channel
  594. ["thumbnail"]
  595. ["thumbnails"]
  596. ) - 1
  597. ]
  598. ["url"]
  599. ),
  600. "ratio" => "1:1"
  601. ],
  602. "url" =>
  603. "https://www.youtube.com/channel/" .
  604. $channel
  605. ["channelId"]
  606. ];
  607. }
  608. // add reels
  609. foreach($items["shortsLockupViewModel"] as $reel){
  610. $out["reel"][] = [
  611. "title" =>
  612. $reel
  613. ["overlayMetadata"]
  614. ["primaryText"]
  615. ["content"],
  616. "description" =>
  617. isset(
  618. $reel
  619. ["overlayMetadata"]
  620. ["primaryText"]
  621. ["content"]
  622. ) ?
  623. $this->titledots(
  624. $reel
  625. ["overlayMetadata"]
  626. ["primaryText"]
  627. ["content"]
  628. ) : null,
  629. "author" => [
  630. "name" => null,
  631. "url" => null,
  632. "avatar" => null
  633. ],
  634. "date" => null,
  635. "duration" => null,
  636. "views" =>
  637. $this->truncatedcount2int(
  638. $reel
  639. ["overlayMetadata"]
  640. ["secondaryText"]
  641. ["content"]
  642. ),
  643. "thumb" => [
  644. "url" =>
  645. $reel
  646. ["thumbnailViewModel"]
  647. ["thumbnailViewModel"]
  648. ["image"]
  649. ["sources"]
  650. [0]
  651. ["url"],
  652. "ratio" => "9:16"
  653. ],
  654. "url" =>
  655. "https://www.youtube.com/watch?v=" .
  656. $reel
  657. ["onTap"]
  658. ["innertubeCommand"]
  659. ["reelWatchEndpoint"]
  660. ["videoId"]
  661. ];
  662. }
  663. // add playlists
  664. foreach($items["lockupViewModel"] as $playlist){
  665. // get author of playlist
  666. $author_name = null;
  667. $author_url = null;
  668. if(
  669. !isset(
  670. $playlist
  671. ["metadata"]
  672. ["lockupMetadataViewModel"]
  673. ["title"]
  674. ["content"]
  675. ) ||
  676. !isset(
  677. $playlist
  678. ["itemPlayback"]
  679. ["inlinePlayerData"]
  680. ["onSelect"]
  681. ["innertubeCommand"]
  682. ["commandMetadata"]
  683. ["webCommandMetadata"]
  684. ["url"]
  685. )
  686. ){
  687. // got an ad viewmodel or some invalid item with no link
  688. // i dont know why the fuck that happens dont ask me
  689. continue;
  690. }
  691. foreach(
  692. $playlist
  693. ["metadata"]
  694. ["lockupMetadataViewModel"]
  695. ["metadata"]
  696. ["contentMetadataViewModel"]
  697. ["metadataRows"]
  698. [0]
  699. ["metadataParts"]
  700. as $possible_author
  701. ){
  702. if(
  703. isset(
  704. $possible_author
  705. ["text"]
  706. ["commandRuns"]
  707. [0]
  708. ["onTap"]
  709. ["innertubeCommand"]
  710. ["commandMetadata"]
  711. ["webCommandMetadata"]
  712. ["webPageType"]
  713. ) &&
  714. $possible_author
  715. ["text"]
  716. ["commandRuns"]
  717. [0]
  718. ["onTap"]
  719. ["innertubeCommand"]
  720. ["commandMetadata"]
  721. ["webCommandMetadata"]
  722. ["webPageType"] == "WEB_PAGE_TYPE_CHANNEL"
  723. ){
  724. $author_name = $possible_author["text"]["content"];
  725. $author_url =
  726. "https://www.youtube.com" .
  727. $possible_author
  728. ["text"]
  729. ["commandRuns"]
  730. [0]
  731. ["onTap"]
  732. ["innertubeCommand"]
  733. ["commandMetadata"]
  734. ["webCommandMetadata"]
  735. ["url"];
  736. break;
  737. }
  738. }
  739. $out["playlist"][] = [
  740. "title" =>
  741. $playlist
  742. ["metadata"]
  743. ["lockupMetadataViewModel"]
  744. ["title"]
  745. ["content"],
  746. "description" => null,
  747. "author" => [
  748. "name" => $author_name,
  749. "url" => $author_url,
  750. "avatar" => null
  751. ],
  752. "date" => null,
  753. "duration" => null,
  754. "views" => null,
  755. "thumb" => [
  756. "url" =>
  757. $playlist
  758. ["contentImage"]
  759. ["collectionThumbnailViewModel"]
  760. ["primaryThumbnail"]
  761. ["thumbnailViewModel"]
  762. ["image"]
  763. ["sources"]
  764. [0]
  765. ["url"],
  766. "ratio" => "16:9"
  767. ],
  768. "url" =>
  769. "https://www.youtube.com" .
  770. $playlist
  771. ["itemPlayback"]
  772. ["inlinePlayerData"]
  773. ["onSelect"]
  774. ["innertubeCommand"]
  775. ["commandMetadata"]
  776. ["webCommandMetadata"]
  777. ["url"]
  778. ];
  779. }
  780. // get next page
  781. $found_npt = false;
  782. if($first_page === true){
  783. // we're using the old pagination trick, google now sends encrypted data
  784. if(count($items["continuationItemRenderer"]) !== 0){
  785. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenWidthPoints"] = 1920;
  786. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenHeightPoints"] = 999;
  787. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenPixelDensity"] = 1;
  788. $ytconfig["INNERTUBE_CONTEXT"]["client"]["screenDensityFloat"] = 1;
  789. $ytconfig["INNERTUBE_CONTEXT"]["client"]["utcOffsetMinutes"] = -240;
  790. $ytconfig["INNERTUBE_CONTEXT"]["client"]["internalExperimentFlags"] = [];
  791. $ytconfig["INNERTUBE_CONTEXT"]["client"]["concistencyTokenJars"] = [];
  792. $ytconfig["INNERTUBE_CONTEXT"]["client"]["mainAppWebInfo"] = [
  793. "graftUrl" => $ytconfig["INNERTUBE_CONTEXT"]["client"]["originalUrl"],
  794. "webDisplayMode" => "WEB_DISPLAY_MODE_BROWSER",
  795. "isWebNativeShareAvailable" => false
  796. ];
  797. $ytconfig["INNERTUBE_CONTEXT"]["adSignalsInfo"] = [
  798. "params" => [
  799. [
  800. "key" => "dt",
  801. "value" => (string)$ytconfig["TIME_CREATED_MS"]
  802. ],
  803. [
  804. "key" => "flash",
  805. "value" => "0"
  806. ],
  807. [
  808. "key" => "frm",
  809. "value" => "0"
  810. ],
  811. [
  812. "key" => "u_tz",
  813. "value" => "-240"
  814. ],
  815. [
  816. "key" => "u_his",
  817. "value" => "3"
  818. ],
  819. [
  820. "key" => "u_h",
  821. "value" => "1080"
  822. ],
  823. [
  824. "key" => "u_w",
  825. "value" => "1920"
  826. ],
  827. [
  828. "key" => "u_ah",
  829. "value" => "1080"
  830. ],
  831. [
  832. "key" => "u_cd",
  833. "value" => "24"
  834. ],
  835. [
  836. "key" => "bc",
  837. "value" => "31"
  838. ],
  839. [
  840. "key" => "bih",
  841. "value" => "999"
  842. ],
  843. [
  844. "key" => "biw",
  845. "value" => "1920"
  846. ],
  847. [
  848. "key" => "brdim",
  849. "value" => "0,0,0,0,1920,0,1920,1061,1920,999"
  850. ],
  851. [
  852. "key" => "vis",
  853. "value" => "1"
  854. ],
  855. [
  856. "key" => "wgl",
  857. "value" => "true"
  858. ],
  859. [
  860. "key" => "ca_type",
  861. "value" => "image"
  862. ]
  863. ]
  864. ];
  865. $found_npt = true;
  866. $npt = [
  867. "key" =>
  868. $ytconfig
  869. ["INNERTUBE_API_KEY"],
  870. "post" => [
  871. "context" =>
  872. $ytconfig
  873. ["INNERTUBE_CONTEXT"],
  874. "continuation" =>
  875. $items
  876. ["continuationItemRenderer"]
  877. [0]
  878. ["continuationEndpoint"]
  879. ["continuationCommand"]
  880. ["token"]
  881. ]
  882. ];
  883. }
  884. }else{
  885. // handle next page for json endpoint
  886. if(count($items["continuationItemRenderer"]) !== 0){
  887. $found_npt = true;
  888. $npt["post"]["continuation"] =
  889. $items
  890. ["continuationItemRenderer"]
  891. [0]
  892. ["continuationEndpoint"]
  893. ["continuationCommand"]
  894. ["token"];
  895. }
  896. }
  897. if($found_npt){
  898. $out["npt"] =
  899. $this->backend->store(
  900. json_encode(
  901. $npt
  902. ),
  903. "videos",
  904. $proxy
  905. );
  906. }
  907. return $out;
  908. }
  909. private function recursive_search(&$search, &$items, &$pointer){
  910. foreach($pointer as $key => $value){
  911. if(
  912. in_array(
  913. $key,
  914. $search
  915. )
  916. ){
  917. // found interesting item
  918. $items[$key][] = $value;
  919. }elseif(is_array($value)){
  920. $this->recursive_search($search, $items, $value);
  921. }
  922. }
  923. }
  924. private function textualdate2unix($number){
  925. $number =
  926. explode(
  927. " ",
  928. str_replace(
  929. [
  930. " ago",
  931. "seconds",
  932. "minutes",
  933. "hours",
  934. "days",
  935. "weeks",
  936. "months",
  937. "years"
  938. ],
  939. [
  940. "",
  941. "second",
  942. "minute",
  943. "hour",
  944. "day",
  945. "week",
  946. "month",
  947. "year"
  948. ],
  949. $number
  950. ),
  951. 2
  952. );
  953. $time = 0;
  954. switch($number[1]){
  955. case "second":
  956. $time = (int)$number[0];
  957. break;
  958. case "minute":
  959. $time = (int)$number[0] * 60;
  960. break;
  961. case "hour":
  962. $time = (int)$number[0] * 3600;
  963. break;
  964. case "day":
  965. $time = (int)$number[0] * 86400;
  966. break;
  967. case "week":
  968. $time = (int)$number[0] * 604800;
  969. break;
  970. case "month":
  971. $time = (int)$number[0] * 2629746;
  972. break;
  973. case "year":
  974. $time = (int)$number[0] * 31556952;
  975. break;
  976. }
  977. return time() - $time;
  978. }
  979. private function checkhttpspresence($link){
  980. if(substr($link, 0, 2) == "//"){
  981. return "https:" . $link;
  982. }
  983. return $link;
  984. }
  985. private function textualtime2int($number){
  986. $number = explode(" - ", $number);
  987. if(count($number) >= 2){
  988. $number = $number[count($number) - 2];
  989. }else{
  990. $number = $number[0];
  991. }
  992. $number =
  993. str_replace(
  994. [
  995. " ",
  996. "seconds",
  997. "minutes",
  998. "hours",
  999. ],
  1000. [
  1001. "",
  1002. "second",
  1003. "minute",
  1004. "hour"
  1005. ],
  1006. $number
  1007. );
  1008. preg_match_all(
  1009. '/([0-9]+)(second|minute|hour)/',
  1010. $number,
  1011. $number
  1012. );
  1013. $time = 0;
  1014. for($i=0; $i<count($number[0]); $i++){
  1015. switch($number[2][$i]){
  1016. case "second":
  1017. $time = $time + (int)$number[1][$i];
  1018. break;
  1019. case "minute":
  1020. $time = $time + ((int)$number[1][$i] * 60);
  1021. break;
  1022. case "hour":
  1023. $time = $time + ((int)$number[1][$i] * 3600);
  1024. break;
  1025. }
  1026. }
  1027. return $time;
  1028. }
  1029. private function views2int($views){
  1030. return
  1031. (int)str_replace(
  1032. ",", "",
  1033. explode(" ", $views, 2)[0]
  1034. );
  1035. }
  1036. private function hms2int($time){
  1037. $parts = explode(":", $time, 3);
  1038. $time = 0;
  1039. if(count($parts) === 3){
  1040. // hours
  1041. $time = $time + ((int)$parts[0] * 3600);
  1042. array_shift($parts);
  1043. }
  1044. if(count($parts) === 2){
  1045. // minutes
  1046. $time = $time + ((int)$parts[0] * 60);
  1047. array_shift($parts);
  1048. }
  1049. // seconds
  1050. $time = $time + (int)$parts[0];
  1051. return $time;
  1052. }
  1053. private function truncatedcount2int($number){
  1054. // decimal should always be 1 number long
  1055. $number = explode(" ", $number, 2);
  1056. $number = $number[0];
  1057. $unit = strtolower($number[strlen($number) - 1]);
  1058. $tmp = explode(".", $number, 2);
  1059. $number = (int)$number;
  1060. if(count($tmp) === 2){
  1061. $decimal = (int)$tmp[1];
  1062. }else{
  1063. $decimal = 0;
  1064. }
  1065. switch($unit){
  1066. case "k":
  1067. $exponant = 1000;
  1068. break;
  1069. case "m":
  1070. $exponant = 1000000;
  1071. break;
  1072. case "b";
  1073. $exponant = 1000000000;
  1074. break;
  1075. default:
  1076. $exponant = 1;
  1077. break;
  1078. }
  1079. return ($number * $exponant) + ($decimal * ($exponant / 10));
  1080. }
  1081. private function titledots($title){
  1082. $substr = substr($title, -3);
  1083. if(
  1084. $substr == "..." ||
  1085. $substr == "…"
  1086. ){
  1087. return str_replace(["\r", "\n"], " ", trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0"));
  1088. }
  1089. return str_replace(["\r", "\n"], " ", trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0"));
  1090. }
  1091. private function assemble_runs($runs){
  1092. if(!isset($runs["runs"])){ return ""; }
  1093. $text = "";
  1094. foreach($runs["runs"] as $run){
  1095. if(isset($run["text"])){
  1096. $text .= $run["text"];
  1097. }
  1098. }
  1099. return $text;
  1100. }
  1101. }