yt.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734
  1. <?php
  2. //$yt = new youtube();
  3. //header("Content-Type: application/json");
  4. //echo json_encode($yt->video("minecraft", null, "today", "any", "any", "live", "relevance"));
  5. class yt{
  6. public function __construct(){
  7. include "lib/backend.php";
  8. $this->backend = new backend("yt");
  9. }
  10. public function getfilters($page){
  11. if($page != "videos"){
  12. return [];
  13. }
  14. return [
  15. "date" => [
  16. "display" => "Time posted",
  17. "option" => [
  18. "any" => "Any time",
  19. "hour" => "Last hour",
  20. "today" => "Today",
  21. "week" => "This week",
  22. "month" => "This month",
  23. "year" => "This year"
  24. ]
  25. ],
  26. "type" => [
  27. "display" => "Type",
  28. "option" => [
  29. "video" => "Video",
  30. "channel" => "Channel",
  31. "playlist" => "Playlist",
  32. "Movie" => "Movie"
  33. ]
  34. ],
  35. "duration" => [
  36. "display" => "Duration",
  37. "option" => [
  38. "any" => "Any duration",
  39. "short" => "Short (>4min)",
  40. "medium" => "Medium (4-20min)",
  41. "long" => "Long (<20min)"
  42. ]
  43. ],
  44. "feature" => [
  45. "display" => "Feature",
  46. "option" => [
  47. "any" => "No features",
  48. "live" => "Live",
  49. "4k" => "4K",
  50. "hd" => "HD",
  51. "subtitles" => "Subtitles/CC",
  52. "creativecommons" => "Creative Commons",
  53. "360" => "VR 360°",
  54. "vr180" => "VR 180°",
  55. "3d" => "3D",
  56. "hdr" => "HDR"
  57. ]
  58. ],
  59. "sort" => [
  60. "display" => "Sort by",
  61. "option" => [
  62. "relevance" => "Relevance",
  63. "upload_date" => "Upload date",
  64. "view_count" => "View count",
  65. "rating" => "Rating"
  66. ]
  67. ]
  68. ];
  69. }
  70. private function ytfilter($date, $type, $duration, $feature, $sort){
  71. // ------------
  72. // INCOMPATIBLE FILTERS
  73. // channel,playlist DURATION, FEATURES, SORT BY
  74. // Movie Features=[live, subtitles, creative commons, 3d]
  75. // live, 3D
  76. // Type[channel, playlist, movie]
  77. // UPLOAD DATE, DURATION, 4k, 360, VR180, HDR
  78. // Type[channel, playlist]
  79. // -----------
  80. // MUST BE TOGETHER
  81. // Relevance,upload date Type=Video
  82. switch($type){
  83. case "channel":
  84. case "playlist":
  85. if($duration != "any"){ $duration = "any"; }
  86. if($feature != "any"){ $feature = "any"; }
  87. if($sort != "any"){ $sort = "any"; }
  88. break;
  89. case "movie":
  90. if(
  91. in_array(
  92. $feature,
  93. [
  94. "live",
  95. "subtitles",
  96. "creative_commons",
  97. "3d"
  98. ],
  99. )
  100. ){
  101. $feature = "any";
  102. }
  103. break;
  104. }
  105. switch($feature){
  106. case "live":
  107. case "3d":
  108. if(
  109. in_array(
  110. $type,
  111. [
  112. "channel",
  113. "playlist",
  114. "movie"
  115. ],
  116. )
  117. ){
  118. $type = "video";
  119. }
  120. break;
  121. }
  122. if(
  123. (
  124. $date != "any" ||
  125. $duration != "any" ||
  126. $feature == "4k" ||
  127. $feature == "360" ||
  128. $feature == "vr180" ||
  129. $feature == "hdr"
  130. ) &&
  131. (
  132. $type == "channel" ||
  133. $type == "playlist"
  134. )
  135. ){
  136. $type = "video";
  137. }
  138. if(
  139. $date == "any" &&
  140. $type == "video" &&
  141. $duration == "any" &&
  142. $feature == "any" &&
  143. $sort == "relevance"
  144. ){
  145. return null;
  146. }
  147. //print_r([$date, $type, $duration, $feature, $sort]);
  148. /*
  149. Encode hex data
  150. */
  151. // UPLOAD DATE
  152. // hour EgQIARAB 12 04 08 01 10 01
  153. // today EgQIAhAB 12 04 08 02 10 01
  154. // week EgQIAxAB 12 04 08 03 10 01
  155. // month EgQIBBAB 12 04 08 04 10 01
  156. // year EgQIBRAB 12 04 08 05 10 01
  157. // TYPE
  158. // video EgIQAQ%253D%253D 12 02 10 01
  159. // channel EgIQAg%253D%253D 12 02 10 02
  160. // playlist EgIQAw%253D%253D 12 02 10 03
  161. // movie EgIQBA%253D%253D 12 02 10 04
  162. // DURATION
  163. // -4min EgIYAQ%253D%253D 12 02 18 01
  164. // 4-20min EgIYAw%253D%253D 12 02 18 03
  165. // 20+min EgIYAg%253D%253D 12 02 18 02
  166. // FEATURE
  167. // live EgJAAQ%253D%253D 12 02 40 01
  168. // 4K EgJwAQ%253D%253D 12 02 70 01
  169. // HD EgIgAQ%253D%253D 12 02 20 01
  170. // Subtitles/CC EgIoAQ%253D%253D 12 02 28 01
  171. // Creative Commons EgIwAQ%253D%253D 12 02 30 01
  172. // 360 EgJ4AQ%253D%253D 12 02 78 01
  173. // VR180 EgPQAQE%253D 12 03 d0 01 01
  174. // 3D EgI4AQ%253D%253D 12 02 38 01
  175. // HDR EgPIAQE%253D 12 03 c8 01 01
  176. // (location & purchased unused)
  177. // SORT BY
  178. // Relevance CAASAhAB 08 00 12 02 10 01 (is nothing by default)
  179. // Upload date CAI%253D 08 02
  180. // View count CAM%253D 08 03
  181. // Rating CAE%253D 08 01
  182. // video
  183. // 12 02 10 01
  184. // under 4 minutes
  185. // 12 02 18 01
  186. // video + under 4 minutes
  187. // 12 04 10 01 18 01
  188. // video + under 4 minutes + HD
  189. // 08 00 12 06 10 01 18 01 20 01
  190. // video + under 4 minutes + upload date
  191. // 08 02 12 04 10 01 18 01
  192. // video + under 4 minutes + HD + upload date
  193. // 08 02 12 06 10 01 18 01 20 01
  194. // this year + video + under 4 minutes + HD + upload date
  195. // 08 02 12 08 08 05 10 01 18 01 20 01
  196. // this week + video + over 20 minutes + HD + view count
  197. // 08 03 12 08 08 03 10 01 18 02 20 01
  198. //echo urlencode(urlencode(base64_encode(hex2bin($str))));
  199. //echo bin2hex(base64_decode(urldecode(urldecode("CAI%253D"))));
  200. // week + video + 20min + rating
  201. // 08 01 12 06 08 03 10 01 18 02
  202. // week + video + 20min + live + rating
  203. // 08 01 12 08 08 03 10 01 18 02 40 01
  204. // live 12 02 40 01
  205. $hex = null;
  206. if(
  207. $date == "any" &&
  208. $type == "video" &&
  209. $duration == "any" &&
  210. $feature == "any" &&
  211. $sort == "relevance"
  212. ){
  213. return $hex;
  214. }
  215. $opcode = 0;
  216. if($date != "any"){ $opcode += 2; }
  217. if($type != "any"){ $opcode += 2; }
  218. if($duration != "any"){ $opcode += 2; }
  219. switch($feature){
  220. case "live":
  221. case "4k":
  222. case "hd":
  223. case "subtitles":
  224. case "creativecommons":
  225. case "360":
  226. case "3d":
  227. $opcode += 2;
  228. break;
  229. case "hdr":
  230. case "vr180":
  231. $opcode += 3;
  232. break;
  233. }
  234. switch($sort){
  235. case "relevance": $hex .= "0800"; break;
  236. case "upload_date": $hex .= "0802"; break;
  237. case "view_count": $hex .= "0803"; break;
  238. case "rating": $hex .= "0801"; break;
  239. }
  240. $hex .= "12" . "0".$opcode;
  241. switch($date){
  242. case "hour": $hex .= "0801"; break;
  243. case "today": $hex .= "0802"; break;
  244. case "week": $hex .= "0803"; break;
  245. case "month": $hex .= "0804"; break;
  246. case "year": $hex .= "0805"; break;
  247. }
  248. switch($type){
  249. case "video": $hex .= "1001"; break;
  250. case "channel": $hex .= "1002"; break;
  251. case "playlist": $hex .= "1003"; break;
  252. case "movie": $hex .= "1004"; break;
  253. }
  254. switch($duration){
  255. case "short": $hex .= "1801"; break;
  256. case "medium": $hex .= "1803"; break;
  257. case "long": $hex .= "1802"; break;
  258. }
  259. switch($feature){
  260. case "live": $hex .= "4001"; break;
  261. case "4k": $hex .= "7001"; break;
  262. case "hd": $hex .= "2001"; break;
  263. case "subtitles": $hex .= "2801"; break;
  264. case "creativecommons": $hex .= "3001"; break;
  265. case "360": $hex .= "7801"; break;
  266. case "vr180": $hex .= "d00101"; break;
  267. case "3d": $hex .= "3801"; break;
  268. case "hdr": $hex .= "c80101"; break;
  269. }
  270. //echo $hex . "\n\n";
  271. return urlencode(base64_encode(hex2bin($hex)));
  272. }
  273. // me reading youtube's json
  274. // https://imgur.com/X9hVlFX
  275. const req_web = 0;
  276. const req_xhr = 1;
  277. private function get($proxy, $url, $get = [], $reqtype = self::req_web, $continuation = null){
  278. $curlproc = curl_init();
  279. if($get !== []){
  280. $get = http_build_query($get);
  281. $url .= "?" . $get;
  282. }
  283. curl_setopt($curlproc, CURLOPT_URL, $url);
  284. switch($reqtype){
  285. case self::req_web:
  286. $headers =
  287. ["User-Agent: " . config::USER_AGENT,
  288. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  289. "Accept-Language: en-US,en;q=0.5",
  290. "Accept-Encoding: gzip",
  291. "Cookie: PREF=tz=America.New_York",
  292. "DNT: 1",
  293. "Connection: keep-alive",
  294. "Upgrade-Insecure-Requests: 1",
  295. "Sec-Fetch-Dest: document",
  296. "Sec-Fetch-Mode: navigate",
  297. "Sec-Fetch-Site: none",
  298. "Sec-Fetch-User: ?1"];
  299. break;
  300. case self::req_xhr:
  301. $headers =
  302. ["User-Agent: " . config::USER_AGENT,
  303. "Accept: */*",
  304. "Accept-Language: en-US,en;q=0.5",
  305. "Accept-Encoding: gzip",
  306. "Cookie: PREF=tz=America.New_York",
  307. "Referer: https://youtube.com.com/",
  308. "Content-Type: application/json",
  309. "Content-Length: " . strlen($continuation),
  310. "DNT: 1",
  311. "Connection: keep-alive",
  312. "Sec-Fetch-Dest: empty",
  313. "Sec-Fetch-Mode: same-origin",
  314. "Sec-Fetch-Site: same-origin"];
  315. curl_setopt($curlproc, CURLOPT_POST, true);
  316. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $continuation);
  317. break;
  318. }
  319. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  320. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  321. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  322. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  323. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  324. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  325. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  326. $this->backend->assign_proxy($curlproc, $proxy);
  327. $data = curl_exec($curlproc);
  328. if(curl_errno($curlproc)){
  329. throw new Exception(curl_error($curlproc));
  330. }
  331. curl_close($curlproc);
  332. return $data;
  333. }
  334. public function video($get){
  335. $this->out = [
  336. "status" => "ok",
  337. "npt" => null,
  338. "video" => [],
  339. "author" => [],
  340. "livestream" => [],
  341. "playlist" => [],
  342. "reel" => []
  343. ];
  344. if($get["npt"]){
  345. // parse nextPage
  346. // https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false
  347. /*
  348. $handle = fopen("nextpage.json", "r");
  349. $json = fread($handle, filesize("nextpage.json"));
  350. fclose($handle);*/
  351. [$npt, $proxy] =
  352. $this->backend->get(
  353. $get["npt"],
  354. "videos"
  355. );
  356. $npt = json_decode($npt, true);
  357. try{
  358. $json = $this->get(
  359. $proxy,
  360. "https://www.youtube.com/youtubei/v1/search",
  361. [
  362. "key" => $npt["key"],
  363. "prettyPrint" => "false"
  364. ],
  365. self::req_xhr,
  366. json_encode($npt["post"])
  367. );
  368. }catch(Exception $error){
  369. throw new Exception("Could not fetch results page");
  370. }
  371. $json = json_decode($json);
  372. foreach(
  373. $json
  374. ->onResponseReceivedCommands[0]
  375. ->appendContinuationItemsAction
  376. ->continuationItems[0]
  377. ->itemSectionRenderer
  378. ->contents
  379. as $video
  380. ){
  381. $this->parsevideoobject($video);
  382. }
  383. if(
  384. !isset(
  385. $json
  386. ->onResponseReceivedCommands[0]
  387. ->appendContinuationItemsAction
  388. ->continuationItems[1]
  389. ->continuationItemRenderer
  390. ->continuationEndpoint
  391. ->continuationCommand
  392. ->token
  393. )
  394. ){
  395. $npt = null;
  396. }else{
  397. // prepare nextpage for later..
  398. $npt["post"]["continuation"] =
  399. $json
  400. ->onResponseReceivedCommands[0]
  401. ->appendContinuationItemsAction
  402. ->continuationItems[1]
  403. ->continuationItemRenderer
  404. ->continuationEndpoint
  405. ->continuationCommand
  406. ->token;
  407. }
  408. $this->out["npt"] = $npt;
  409. }else{
  410. $search = $get["s"];
  411. if(strlen($search) === 0){
  412. throw new Exception("Search term is empty!");
  413. }
  414. $proxy = $this->backend->get_ip();
  415. $date = $get["date"];
  416. $type = $get["type"];
  417. $duration = $get["duration"];
  418. $feature = $get["feature"];
  419. $sort = $get["sort"];
  420. // parse ytInitialData
  421. $get = [
  422. "search_query" => $search
  423. ];
  424. if(
  425. (
  426. $filter =
  427. $this->ytfilter(
  428. $date,
  429. $type,
  430. $duration,
  431. $feature,
  432. $sort
  433. )
  434. ) !== null
  435. ){
  436. $get["sp"] = $filter;
  437. }
  438. try{
  439. $json = $this->get(
  440. $proxy,
  441. "https://www.youtube.com/results",
  442. $get
  443. );
  444. }catch(Exception $error){
  445. throw new Exception("Could not fetch results page");
  446. }
  447. /*
  448. $handle = fopen("test.html", "r");
  449. $json = fread($handle, filesize("test.html"));
  450. fclose($handle);
  451. */
  452. if(
  453. !preg_match(
  454. '/ytcfg\.set\(({".*})\); *window\.ytcfg/',
  455. $json,
  456. $ytconfig
  457. )
  458. ){
  459. throw new Exception("Could not get ytcfg");
  460. }
  461. $ytconfig = json_decode($ytconfig[1]);
  462. if(
  463. !preg_match(
  464. '/ytInitialData *= *({.*});<\/script>/',
  465. $json,
  466. $json
  467. )
  468. ){
  469. throw new Exception("Could not get ytInitialData");
  470. }
  471. $json = json_decode($json[1]);
  472. // generate POST data for nextpage
  473. $ytconfig->INNERTUBE_CONTEXT->client->screenWidthPoints = 1239;
  474. $ytconfig->INNERTUBE_CONTEXT->client->screenHeightPoints = 999;
  475. $ytconfig->INNERTUBE_CONTEXT->client->screenPixelDensity = 1;
  476. $ytconfig->INNERTUBE_CONTEXT->client->screenDensityFloat = 1;
  477. $ytconfig->INNERTUBE_CONTEXT->client->utcOffsetMinutes = -240;
  478. $ytconfig->INNERTUBE_CONTEXT->request->internalExperimentFlags = [];
  479. $ytconfig->INNERTUBE_CONTEXT->request->consistencyTokenJars = [];
  480. $ytconfig->INNERTUBE_CONTEXT->client->mainAppWebInfo = [
  481. "graftUrl" => $ytconfig->INNERTUBE_CONTEXT->client->originalUrl,
  482. "webDisplayMode" => "WEB_DISPLAY_MODE_BROWSER",
  483. "isWebNativeShareAvailable" => false
  484. ];
  485. $ytconfig->INNERTUBE_CONTEXT->adSignalsInfo = [
  486. "params" => [
  487. [
  488. "key" => "dt",
  489. "value" => (string)$ytconfig->TIME_CREATED_MS
  490. ],
  491. [
  492. "key" => "flash",
  493. "value" => "0"
  494. ],
  495. [
  496. "key" => "frm",
  497. "value" => "0"
  498. ],
  499. [
  500. "key" => "u_tz",
  501. "value" => "-240"
  502. ],
  503. [
  504. "key" => "u_his",
  505. "value" => "3"
  506. ],
  507. [
  508. "key" => "u_h",
  509. "value" => "1080"
  510. ],
  511. [
  512. "key" => "u_w",
  513. "value" => "1920"
  514. ],
  515. [
  516. "key" => "u_ah",
  517. "value" => "1080"
  518. ],
  519. [
  520. "key" => "u_cd",
  521. "value" => "24"
  522. ],
  523. [
  524. "key" => "bc",
  525. "value" => "31"
  526. ],
  527. [
  528. "key" => "bih",
  529. "value" => "999"
  530. ],
  531. [
  532. "key" => "biw",
  533. "value" => "1239"
  534. ],
  535. [
  536. "key" => "brdim",
  537. "value" => "0,0,0,0,1920,0,1920,1061,1239,999"
  538. ],
  539. [
  540. "key" => "vis",
  541. "value" => "1"
  542. ],
  543. [
  544. "key" => "wgl",
  545. "value" => "true"
  546. ],
  547. [
  548. "key" => "ca_type",
  549. "value" => "image"
  550. ]
  551. ]
  552. ];
  553. /*
  554. echo json_encode($json);
  555. die();*/
  556. // *inhales*
  557. foreach(
  558. $json
  559. ->contents
  560. ->twoColumnSearchResultsRenderer
  561. ->primaryContents
  562. ->sectionListRenderer
  563. ->contents[0]
  564. ->itemSectionRenderer
  565. ->contents
  566. as $video
  567. ){
  568. $this->parsevideoobject($video);
  569. }
  570. // get additional data from secondaryContents
  571. if(
  572. isset(
  573. $json
  574. ->contents
  575. ->twoColumnSearchResultsRenderer
  576. ->secondaryContents
  577. ->secondarySearchContainerRenderer
  578. ->contents[0]
  579. ->universalWatchCardRenderer
  580. )
  581. ){
  582. $video =
  583. $json
  584. ->contents
  585. ->twoColumnSearchResultsRenderer
  586. ->secondaryContents
  587. ->secondarySearchContainerRenderer
  588. ->contents[0]
  589. ->universalWatchCardRenderer;
  590. /*
  591. echo json_encode($video);
  592. die();*/
  593. $author =
  594. [
  595. "name" =>
  596. $video
  597. ->header
  598. ->watchCardRichHeaderRenderer
  599. ->title
  600. ->simpleText,
  601. "url" =>
  602. "https://www.youtube.com/channel/" .
  603. $video
  604. ->header
  605. ->watchCardRichHeaderRenderer
  606. ->titleNavigationEndpoint
  607. ->browseEndpoint
  608. ->browseId,
  609. "avatar" => null
  610. ];
  611. if(
  612. isset(
  613. $video
  614. ->header
  615. ->watchCardRichHeaderRenderer
  616. ->avatar
  617. ->thumbnails[0]
  618. ->url
  619. )
  620. ){
  621. $author["avatar"] =
  622. $video
  623. ->header
  624. ->watchCardRichHeaderRenderer
  625. ->avatar
  626. ->thumbnails[0]
  627. ->url;
  628. }
  629. // add video in callToAction if present
  630. if(
  631. isset(
  632. $video
  633. ->callToAction
  634. ->watchCardHeroVideoRenderer
  635. ->lengthText
  636. )
  637. ){
  638. array_push(
  639. $this->out["video"],
  640. [
  641. "title" =>
  642. $video
  643. ->callToAction
  644. ->watchCardHeroVideoRenderer
  645. ->title
  646. ->simpleText,
  647. "description" => null,
  648. "author" => $author,
  649. "date" =>
  650. $this->textualdate2unix(
  651. trim(
  652. explode(
  653. "•",
  654. $video
  655. ->callToAction
  656. ->watchCardHeroVideoRenderer
  657. ->subtitle
  658. ->simpleText
  659. )[2]
  660. )
  661. ),
  662. "duration" =>
  663. $this->hms2int(
  664. $video
  665. ->callToAction
  666. ->watchCardHeroVideoRenderer
  667. ->lengthText
  668. ->simpleText
  669. ),
  670. "views" =>
  671. $this->truncatedcount2int(
  672. trim(
  673. explode(
  674. "•",
  675. $video
  676. ->callToAction
  677. ->watchCardHeroVideoRenderer
  678. ->subtitle
  679. ->simpleText,
  680. 2
  681. )[1]
  682. )
  683. ),
  684. "thumb" => [
  685. "url" =>
  686. $video
  687. ->callToAction
  688. ->watchCardHeroVideoRenderer
  689. ->heroImage
  690. ->singleHeroImageRenderer
  691. ->thumbnail
  692. ->thumbnails[0]
  693. ->url,
  694. "ratio" => "16:9"
  695. ],
  696. "url" =>
  697. "https://www.youtube.com/watch?v=" .
  698. $video
  699. ->callToAction
  700. ->watchCardHeroVideoRenderer
  701. ->navigationEndpoint
  702. ->watchEndpoint
  703. ->videoId
  704. ]
  705. );
  706. }
  707. // get all playlists, ignore videos
  708. $out = null;
  709. foreach(
  710. $video
  711. ->sections
  712. as $section
  713. ){
  714. if(
  715. isset(
  716. $section
  717. ->watchCardSectionSequenceRenderer
  718. ->lists[0]
  719. ->horizontalCardListRenderer
  720. ->cards
  721. )
  722. ){
  723. $out =
  724. $section
  725. ->watchCardSectionSequenceRenderer
  726. ->lists[0]
  727. ->horizontalCardListRenderer
  728. ->cards;
  729. break;
  730. }
  731. }
  732. if($out !== null){
  733. foreach(
  734. $out as $video
  735. ){
  736. if(
  737. !isset(
  738. $video
  739. ->searchRefinementCardRenderer
  740. )
  741. ){
  742. continue;
  743. }
  744. $video =
  745. $video
  746. ->searchRefinementCardRenderer;
  747. array_push(
  748. $this->out["playlist"],
  749. [
  750. "title" =>
  751. $video
  752. ->query
  753. ->runs[0]
  754. ->text,
  755. "description" => null,
  756. "author" => $author,
  757. "date" => null,
  758. "duration" => null,
  759. "views" => null,
  760. "thumb" => [
  761. "url" =>
  762. $video
  763. ->thumbnail
  764. ->thumbnails[0]
  765. ->url,
  766. "ratio" => "1:1"
  767. ],
  768. "url" =>
  769. "https://www.youtube.com" .
  770. $video
  771. ->searchEndpoint
  772. ->commandMetadata
  773. ->webCommandMetadata
  774. ->url
  775. ]
  776. );
  777. }
  778. }
  779. }
  780. foreach(
  781. $json
  782. ->contents
  783. ->twoColumnSearchResultsRenderer
  784. ->primaryContents
  785. ->sectionListRenderer
  786. ->contents
  787. as $cont
  788. ){
  789. if(isset($cont->continuationItemRenderer)){
  790. $this->out["npt"] = [
  791. "key" =>
  792. $ytconfig
  793. ->INNERTUBE_API_KEY,
  794. "post" => [
  795. "context" =>
  796. $ytconfig
  797. ->INNERTUBE_CONTEXT,
  798. "continuation" =>
  799. $cont
  800. ->continuationItemRenderer
  801. ->continuationEndpoint
  802. ->continuationCommand
  803. ->token
  804. ]
  805. ];
  806. break;
  807. }
  808. }
  809. }
  810. if($this->out["npt"] !== null){
  811. $this->out["npt"] =
  812. $this->backend->store(
  813. json_encode(
  814. $this->out["npt"]
  815. ),
  816. "videos",
  817. $proxy
  818. );
  819. }
  820. return $this->out;
  821. }
  822. private function parsevideoobject($video){
  823. if(isset($video->videoRenderer)){
  824. $video = $video->videoRenderer;
  825. $description = null;
  826. if(isset($video->detailedMetadataSnippets)){
  827. foreach(
  828. $video
  829. ->detailedMetadataSnippets[0]
  830. ->snippetText
  831. ->runs
  832. as $description_part
  833. ){
  834. $description .= $description_part->text;
  835. }
  836. }
  837. if(
  838. isset(
  839. $video
  840. ->badges[0]
  841. ->metadataBadgeRenderer
  842. ->icon
  843. ->iconType
  844. ) &&
  845. $video
  846. ->badges[0]
  847. ->metadataBadgeRenderer
  848. ->icon
  849. ->iconType
  850. == "LIVE"
  851. ){
  852. $type = "livestream";
  853. $date = null;
  854. $duration = "_LIVE";
  855. if(isset($video->viewCountText->runs[0]->text)){
  856. $views =
  857. $this->views2int(
  858. $video
  859. ->viewCountText
  860. ->runs[0]
  861. ->text
  862. );
  863. }else{
  864. $views = null;
  865. }
  866. }else{
  867. $type = "video";
  868. if(isset($video->publishedTimeText->simpleText)){
  869. $date = $this->textualdate2unix(
  870. $video
  871. ->publishedTimeText
  872. ->simpleText
  873. );
  874. }else{
  875. $date = null;
  876. }
  877. if(isset($video->lengthText->simpleText)){
  878. $duration =
  879. $this->hms2int(
  880. $video
  881. ->lengthText
  882. ->simpleText
  883. );
  884. }else{
  885. $duration = null;
  886. }
  887. if(isset($video->viewCountText->simpleText)){
  888. $views =
  889. $this->views2int(
  890. $video
  891. ->viewCountText
  892. ->simpleText
  893. );
  894. }else{
  895. $views = null;
  896. }
  897. }
  898. if(
  899. $video
  900. ->navigationEndpoint
  901. ->commandMetadata
  902. ->webCommandMetadata
  903. ->webPageType
  904. == "WEB_PAGE_TYPE_SHORTS"
  905. ){
  906. // haha you thought you could get me, youtube
  907. // jokes on you i dont go outside
  908. $type = "reel";
  909. }
  910. array_push(
  911. $this->out[$type],
  912. [
  913. "title" =>
  914. $video
  915. ->title
  916. ->runs[0]
  917. ->text,
  918. "description" =>
  919. $this->titledots($description),
  920. "author" => [
  921. "name" =>
  922. $video
  923. ->longBylineText
  924. ->runs[0]
  925. ->text,
  926. "url" =>
  927. "https://www.youtube.com/channel/" .
  928. $video
  929. ->longBylineText
  930. ->runs[0]
  931. ->navigationEndpoint
  932. ->browseEndpoint
  933. ->browseId,
  934. "avatar" =>
  935. $this->checkhttpspresence(
  936. $video
  937. ->channelThumbnailSupportedRenderers
  938. ->channelThumbnailWithLinkRenderer
  939. ->thumbnail
  940. ->thumbnails[0]
  941. ->url
  942. )
  943. ],
  944. "date" => $date,
  945. "duration" => $duration,
  946. "views" => $views,
  947. "thumb" => [
  948. "url" =>
  949. $video
  950. ->thumbnail
  951. ->thumbnails[0]
  952. ->url,
  953. "ratio" => "16:9"
  954. ],
  955. "url" =>
  956. "https://www.youtube.com/watch?v=" .
  957. $video
  958. ->videoId
  959. ]
  960. );
  961. }elseif(isset($video->watchCardCompactVideoRenderer)){
  962. $video =
  963. $video
  964. ->watchCardCompactVideoRenderer;
  965. array_push(
  966. $this->out["video"],
  967. [
  968. "title" =>
  969. $video
  970. ->title
  971. ->simpleText,
  972. "description" => null,
  973. "author" => [
  974. "name" =>
  975. $video
  976. ->byline
  977. ->runs[0]
  978. ->text,
  979. "url" =>
  980. "https://www.youtube.com/channel/" .
  981. $video
  982. ->byline
  983. ->runs[0]
  984. ->navigationEndpoint
  985. ->browseEndpoint
  986. ->browseId,
  987. "avatar" => null
  988. ],
  989. "date" =>
  990. $this->textualdate2unix(
  991. trim(
  992. explode(
  993. "•",
  994. $video
  995. ->subtitle
  996. ->simpleText,
  997. 2
  998. )[1]
  999. )
  1000. ),
  1001. "duration" =>
  1002. $this->hms2int(
  1003. $video
  1004. ->lengthText
  1005. ->simpleText
  1006. ),
  1007. "views" =>
  1008. $this->truncatedcount2int(
  1009. trim(
  1010. explode(
  1011. "•",
  1012. $video
  1013. ->subtitle
  1014. ->simpleText,
  1015. 2
  1016. )[0]
  1017. )
  1018. ),
  1019. "thumb" => [
  1020. "url" =>
  1021. $video
  1022. ->thumbnail
  1023. ->thumbnails[0]
  1024. ->url,
  1025. "ratio" => "16:9"
  1026. ],
  1027. "url" =>
  1028. "https://www.youtube.com/watch?v=" .
  1029. $video
  1030. ->navigationEndpoint
  1031. ->watchEndpoint
  1032. ->videoId
  1033. ]
  1034. );
  1035. }elseif(isset($video->reelShelfRenderer)){
  1036. foreach(
  1037. $video
  1038. ->reelShelfRenderer
  1039. ->items
  1040. as $reel
  1041. ){
  1042. $reel =
  1043. $reel
  1044. ->reelItemRenderer;
  1045. array_push(
  1046. $this->out["reel"],
  1047. [
  1048. "title" =>
  1049. $reel
  1050. ->headline
  1051. ->simpleText,
  1052. "description" => null,
  1053. "author" => [
  1054. "name" => null,
  1055. "url" => null,
  1056. "avatar" => null
  1057. ],
  1058. "date" => null,
  1059. "duration" =>
  1060. $this->textualtime2int(
  1061. $reel
  1062. ->accessibility
  1063. ->accessibilityData
  1064. ->label
  1065. ),
  1066. "views" =>
  1067. $this->truncatedcount2int(
  1068. $reel
  1069. ->viewCountText
  1070. ->simpleText
  1071. ),
  1072. "thumb" => [
  1073. "url" =>
  1074. $reel
  1075. ->thumbnail
  1076. ->thumbnails[0]
  1077. ->url,
  1078. "ratio" => "9:16"
  1079. ],
  1080. "url" =>
  1081. "https://www.youtube.com/watch?v=" .
  1082. $reel
  1083. ->videoId
  1084. ]
  1085. );
  1086. }
  1087. }
  1088. elseif(isset($video->channelRenderer)){
  1089. $video = $video->channelRenderer;
  1090. $description = null;
  1091. if(isset($video->descriptionSnippet)){
  1092. foreach(
  1093. $video
  1094. ->descriptionSnippet
  1095. ->runs
  1096. as $description_part
  1097. ){
  1098. $description .= $description_part->text;
  1099. }
  1100. }
  1101. array_push(
  1102. $this->out["author"],
  1103. [
  1104. "title" =>
  1105. $video
  1106. ->title
  1107. ->simpleText,
  1108. "followers" =>
  1109. isset(
  1110. $video
  1111. ->videoCountText
  1112. ->simpleText
  1113. ) ?
  1114. $this->truncatedcount2int(
  1115. $video
  1116. ->videoCountText
  1117. ->simpleText
  1118. ) :
  1119. 0,
  1120. "description" => $this->titledots($description),
  1121. "thumb" =>
  1122. [
  1123. "url" =>
  1124. $this->checkhttpspresence(
  1125. $video
  1126. ->thumbnail
  1127. ->thumbnails[
  1128. count(
  1129. $video
  1130. ->thumbnail
  1131. ->thumbnails
  1132. ) - 1
  1133. ]
  1134. ->url
  1135. ),
  1136. "ratio" => "1:1"
  1137. ],
  1138. "url" =>
  1139. "https://www.youtube.com/channel/" .
  1140. $video
  1141. ->channelId
  1142. ]
  1143. );
  1144. }
  1145. elseif(isset($video->shelfRenderer)){
  1146. if(
  1147. !is_object(
  1148. $video
  1149. ->shelfRenderer
  1150. ->content
  1151. ->verticalListRenderer
  1152. )
  1153. ){
  1154. return;
  1155. }
  1156. foreach(
  1157. $video
  1158. ->shelfRenderer
  1159. ->content
  1160. ->verticalListRenderer
  1161. ->items
  1162. as $shelfvideo
  1163. ){
  1164. $this->parsevideoobject($shelfvideo);
  1165. }
  1166. }elseif(isset($video->radioRenderer)){
  1167. $video = $video->radioRenderer;
  1168. $description =
  1169. $video
  1170. ->videoCountText
  1171. ->runs[0]
  1172. ->text
  1173. . ".";
  1174. $tmp = [];
  1175. foreach(
  1176. $video->videos
  1177. as $childvideo
  1178. ){
  1179. $tmp[] =
  1180. $childvideo
  1181. ->childVideoRenderer
  1182. ->title
  1183. ->simpleText;
  1184. }
  1185. if(count($tmp) !== 0){
  1186. $description .=
  1187. " " . implode(", ", $tmp);
  1188. }
  1189. array_push(
  1190. $this->out["playlist"],
  1191. [
  1192. "title" =>
  1193. $video
  1194. ->title
  1195. ->simpleText,
  1196. "description" => $description,
  1197. "author" => [
  1198. "name" =>
  1199. $video
  1200. ->longBylineText
  1201. ->simpleText,
  1202. "url" => null,
  1203. "avatar" => null
  1204. ],
  1205. "date" => null,
  1206. "duration" => null,
  1207. "views" => null,
  1208. "thumb" => [
  1209. "url" =>
  1210. $video
  1211. ->thumbnail
  1212. ->thumbnails[
  1213. count(
  1214. $video
  1215. ->thumbnail
  1216. ->thumbnails
  1217. ) - 1
  1218. ]
  1219. ->url,
  1220. "ratio" => "16:9"
  1221. ],
  1222. "url" =>
  1223. "https://www.youtube.com/watch?v=" .
  1224. $video
  1225. ->videos[0]
  1226. ->childVideoRenderer
  1227. ->videoId .
  1228. "&list=" .
  1229. $video
  1230. ->playlistId .
  1231. "&start_radio=1"
  1232. ]
  1233. );
  1234. }elseif(isset($video->playlistRenderer)){
  1235. $video = $video->playlistRenderer;
  1236. $description = $video->videoCount . " videos.";
  1237. $tmp = [];
  1238. foreach(
  1239. $video
  1240. ->videos
  1241. as $childvideo
  1242. ){
  1243. $tmp[] =
  1244. $childvideo
  1245. ->childVideoRenderer
  1246. ->title
  1247. ->simpleText;
  1248. }
  1249. if(count($tmp) !== 0){
  1250. $description .=
  1251. " " . implode(", ", $tmp);
  1252. }
  1253. array_push(
  1254. $this->out["playlist"],
  1255. [
  1256. "title" =>
  1257. $video
  1258. ->title
  1259. ->simpleText,
  1260. "description" => $description,
  1261. "author" => [
  1262. "name" =>
  1263. $video
  1264. ->longBylineText
  1265. ->runs[0]
  1266. ->text,
  1267. "url" =>
  1268. "https://www.youtube.com/channel/" .
  1269. $video
  1270. ->longBylineText
  1271. ->runs[0]
  1272. ->navigationEndpoint
  1273. ->browseEndpoint
  1274. ->browseId,
  1275. "picture" => null
  1276. ],
  1277. "date" => null,
  1278. "duration" => null,
  1279. "views" => null,
  1280. "thumb" =>
  1281. [
  1282. "url" =>
  1283. $video
  1284. ->thumbnails[0]
  1285. ->thumbnails[
  1286. count(
  1287. $video
  1288. ->thumbnails[0]
  1289. ->thumbnails
  1290. ) - 1
  1291. ]
  1292. ->url,
  1293. "ratio" => "16:9"
  1294. ],
  1295. "url" =>
  1296. "https://www.youtube.com/watch?v=" .
  1297. $video
  1298. ->videos[0]
  1299. ->childVideoRenderer
  1300. ->videoId .
  1301. "&list=" .
  1302. $video
  1303. ->playlistId .
  1304. "&start_radio=1"
  1305. ]
  1306. );
  1307. }/*else{
  1308. if(!isset($video->searchPyvRenderer)){
  1309. echo json_encode($video);
  1310. die();}
  1311. }*/
  1312. }
  1313. private function textualdate2unix($number){
  1314. $number =
  1315. explode(
  1316. " ",
  1317. str_replace(
  1318. [
  1319. " ago",
  1320. "seconds",
  1321. "minutes",
  1322. "hours",
  1323. "days",
  1324. "weeks",
  1325. "months",
  1326. "years"
  1327. ],
  1328. [
  1329. "",
  1330. "second",
  1331. "minute",
  1332. "hour",
  1333. "day",
  1334. "week",
  1335. "month",
  1336. "year"
  1337. ],
  1338. $number
  1339. ),
  1340. 2
  1341. );
  1342. $time = 0;
  1343. switch($number[1]){
  1344. case "second":
  1345. $time = (int)$number[0];
  1346. break;
  1347. case "minute":
  1348. $time = (int)$number[0] * 60;
  1349. break;
  1350. case "hour":
  1351. $time = (int)$number[0] * 3600;
  1352. break;
  1353. case "day":
  1354. $time = (int)$number[0] * 86400;
  1355. break;
  1356. case "week":
  1357. $time = (int)$number[0] * 604800;
  1358. break;
  1359. case "month":
  1360. $time = (int)$number[0] * 2629746;
  1361. break;
  1362. case "year":
  1363. $time = (int)$number[0] * 31556952;
  1364. break;
  1365. }
  1366. return time() - $time;
  1367. }
  1368. private function checkhttpspresence($link){
  1369. if(substr($link, 0, 2) == "//"){
  1370. return "https:" . $link;
  1371. }
  1372. return $link;
  1373. }
  1374. private function textualtime2int($number){
  1375. $number = explode(" - ", $number);
  1376. if(count($number) >= 2){
  1377. $number = $number[count($number) - 2];
  1378. }else{
  1379. $number = $number[0];
  1380. }
  1381. $number =
  1382. str_replace(
  1383. [
  1384. " ",
  1385. "seconds",
  1386. "minutes",
  1387. "hours",
  1388. ],
  1389. [
  1390. "",
  1391. "second",
  1392. "minute",
  1393. "hour"
  1394. ],
  1395. $number
  1396. );
  1397. preg_match_all(
  1398. '/([0-9]+)(second|minute|hour)/',
  1399. $number,
  1400. $number
  1401. );
  1402. $time = 0;
  1403. for($i=0; $i<count($number[0]); $i++){
  1404. switch($number[2][$i]){
  1405. case "second":
  1406. $time = $time + (int)$number[1][$i];
  1407. break;
  1408. case "minute":
  1409. $time = $time + ((int)$number[1][$i] * 60);
  1410. break;
  1411. case "hour":
  1412. $time = $time + ((int)$number[1][$i] * 3600);
  1413. break;
  1414. }
  1415. }
  1416. return $time;
  1417. }
  1418. private function views2int($views){
  1419. return
  1420. (int)str_replace(
  1421. ",", "",
  1422. explode(" ", $views, 2)[0]
  1423. );
  1424. }
  1425. private function hms2int($time){
  1426. $parts = explode(":", $time, 3);
  1427. $time = 0;
  1428. if(count($parts) === 3){
  1429. // hours
  1430. $time = $time + ((int)$parts[0] * 3600);
  1431. array_shift($parts);
  1432. }
  1433. if(count($parts) === 2){
  1434. // minutes
  1435. $time = $time + ((int)$parts[0] * 60);
  1436. array_shift($parts);
  1437. }
  1438. // seconds
  1439. $time = $time + (int)$parts[0];
  1440. return $time;
  1441. }
  1442. private function truncatedcount2int($number){
  1443. // decimal should always be 1 number long
  1444. $number = explode(" ", $number, 2);
  1445. $number = $number[0];
  1446. $unit = strtolower($number[strlen($number) - 1]);
  1447. $tmp = explode(".", $number, 2);
  1448. $number = (int)$number;
  1449. if(count($tmp) === 2){
  1450. $decimal = (int)$tmp[1];
  1451. }else{
  1452. $decimal = 0;
  1453. }
  1454. switch($unit){
  1455. case "k":
  1456. $exponant = 1000;
  1457. break;
  1458. case "m":
  1459. $exponant = 1000000;
  1460. break;
  1461. case "b";
  1462. $exponant = 1000000000;
  1463. break;
  1464. default:
  1465. $exponant = 1;
  1466. break;
  1467. }
  1468. return ($number * $exponant) + ($decimal * ($exponant / 10));
  1469. }
  1470. private function titledots($title){
  1471. $substr = substr($title, -3);
  1472. if(
  1473. $substr == "..." ||
  1474. $substr == "…"
  1475. ){
  1476. return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
  1477. }
  1478. return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
  1479. }
  1480. }