yt.php 25 KB

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