1735 lines
32 KiB
PHP
1735 lines
32 KiB
PHP
<?php
|
|
|
|
//$yt = new youtube();
|
|
//header("Content-Type: application/json");
|
|
//echo json_encode($yt->video("minecraft", null, "today", "any", "any", "live", "relevance"));
|
|
|
|
class yt{
|
|
|
|
public function __construct(){
|
|
|
|
include "lib/backend.php";
|
|
$this->backend = new backend("yt");
|
|
}
|
|
|
|
public function getfilters($page){
|
|
|
|
if($page != "videos"){
|
|
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
"date" => [
|
|
"display" => "Time posted",
|
|
"option" => [
|
|
"any" => "Any time",
|
|
"hour" => "Last hour",
|
|
"today" => "Today",
|
|
"week" => "This week",
|
|
"month" => "This month",
|
|
"year" => "This year"
|
|
]
|
|
],
|
|
"type" => [
|
|
"display" => "Type",
|
|
"option" => [
|
|
"video" => "Video",
|
|
"channel" => "Channel",
|
|
"playlist" => "Playlist",
|
|
"Movie" => "Movie"
|
|
]
|
|
],
|
|
"duration" => [
|
|
"display" => "Duration",
|
|
"option" => [
|
|
"any" => "Any duration",
|
|
"short" => "Short (>4min)",
|
|
"medium" => "Medium (4-20min)",
|
|
"long" => "Long (<20min)"
|
|
]
|
|
],
|
|
"feature" => [
|
|
"display" => "Feature",
|
|
"option" => [
|
|
"any" => "No features",
|
|
"live" => "Live",
|
|
"4k" => "4K",
|
|
"hd" => "HD",
|
|
"subtitles" => "Subtitles/CC",
|
|
"creativecommons" => "Creative Commons",
|
|
"360" => "VR 360°",
|
|
"vr180" => "VR 180°",
|
|
"3d" => "3D",
|
|
"hdr" => "HDR"
|
|
]
|
|
],
|
|
"sort" => [
|
|
"display" => "Sort by",
|
|
"option" => [
|
|
"relevance" => "Relevance",
|
|
"upload_date" => "Upload date",
|
|
"view_count" => "View count",
|
|
"rating" => "Rating"
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
private function ytfilter($date, $type, $duration, $feature, $sort){
|
|
|
|
// ------------
|
|
// INCOMPATIBLE FILTERS
|
|
// channel,playlist DURATION, FEATURES, SORT BY
|
|
// Movie Features=[live, subtitles, creative commons, 3d]
|
|
|
|
// live, 3D
|
|
// Type[channel, playlist, movie]
|
|
|
|
// UPLOAD DATE, DURATION, 4k, 360, VR180, HDR
|
|
// Type[channel, playlist]
|
|
|
|
// -----------
|
|
|
|
// MUST BE TOGETHER
|
|
// Relevance,upload date Type=Video
|
|
|
|
switch($type){
|
|
|
|
case "channel":
|
|
case "playlist":
|
|
if($duration != "any"){ $duration = "any"; }
|
|
if($feature != "any"){ $feature = "any"; }
|
|
if($sort != "any"){ $sort = "any"; }
|
|
break;
|
|
|
|
case "movie":
|
|
if(
|
|
in_array(
|
|
$feature,
|
|
[
|
|
"live",
|
|
"subtitles",
|
|
"creative_commons",
|
|
"3d"
|
|
],
|
|
)
|
|
){
|
|
|
|
$feature = "any";
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch($feature){
|
|
|
|
case "live":
|
|
case "3d":
|
|
if(
|
|
in_array(
|
|
$type,
|
|
[
|
|
"channel",
|
|
"playlist",
|
|
"movie"
|
|
],
|
|
)
|
|
){
|
|
|
|
$type = "video";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(
|
|
(
|
|
$date != "any" ||
|
|
$duration != "any" ||
|
|
$feature == "4k" ||
|
|
$feature == "360" ||
|
|
$feature == "vr180" ||
|
|
$feature == "hdr"
|
|
) &&
|
|
(
|
|
$type == "channel" ||
|
|
$type == "playlist"
|
|
)
|
|
){
|
|
|
|
$type = "video";
|
|
}
|
|
|
|
if(
|
|
$date == "any" &&
|
|
$type == "video" &&
|
|
$duration == "any" &&
|
|
$feature == "any" &&
|
|
$sort == "relevance"
|
|
){
|
|
|
|
return null;
|
|
}
|
|
|
|
//print_r([$date, $type, $duration, $feature, $sort]);
|
|
|
|
/*
|
|
Encode hex data
|
|
*/
|
|
|
|
// UPLOAD DATE
|
|
// hour EgQIARAB 12 04 08 01 10 01
|
|
// today EgQIAhAB 12 04 08 02 10 01
|
|
// week EgQIAxAB 12 04 08 03 10 01
|
|
// month EgQIBBAB 12 04 08 04 10 01
|
|
// year EgQIBRAB 12 04 08 05 10 01
|
|
|
|
// TYPE
|
|
// video EgIQAQ%253D%253D 12 02 10 01
|
|
// channel EgIQAg%253D%253D 12 02 10 02
|
|
// playlist EgIQAw%253D%253D 12 02 10 03
|
|
// movie EgIQBA%253D%253D 12 02 10 04
|
|
|
|
// DURATION
|
|
// -4min EgIYAQ%253D%253D 12 02 18 01
|
|
// 4-20min EgIYAw%253D%253D 12 02 18 03
|
|
// 20+min EgIYAg%253D%253D 12 02 18 02
|
|
|
|
// FEATURE
|
|
// live EgJAAQ%253D%253D 12 02 40 01
|
|
// 4K EgJwAQ%253D%253D 12 02 70 01
|
|
// HD EgIgAQ%253D%253D 12 02 20 01
|
|
// Subtitles/CC EgIoAQ%253D%253D 12 02 28 01
|
|
// Creative Commons EgIwAQ%253D%253D 12 02 30 01
|
|
// 360 EgJ4AQ%253D%253D 12 02 78 01
|
|
// VR180 EgPQAQE%253D 12 03 d0 01 01
|
|
// 3D EgI4AQ%253D%253D 12 02 38 01
|
|
// HDR EgPIAQE%253D 12 03 c8 01 01
|
|
// (location & purchased unused)
|
|
|
|
// SORT BY
|
|
// Relevance CAASAhAB 08 00 12 02 10 01 (is nothing by default)
|
|
// Upload date CAI%253D 08 02
|
|
// View count CAM%253D 08 03
|
|
// Rating CAE%253D 08 01
|
|
|
|
// video
|
|
// 12 02 10 01
|
|
|
|
// under 4 minutes
|
|
// 12 02 18 01
|
|
|
|
// video + under 4 minutes
|
|
// 12 04 10 01 18 01
|
|
|
|
// video + under 4 minutes + HD
|
|
// 08 00 12 06 10 01 18 01 20 01
|
|
|
|
// video + under 4 minutes + upload date
|
|
// 08 02 12 04 10 01 18 01
|
|
|
|
// video + under 4 minutes + HD + upload date
|
|
// 08 02 12 06 10 01 18 01 20 01
|
|
|
|
// this year + video + under 4 minutes + HD + upload date
|
|
// 08 02 12 08 08 05 10 01 18 01 20 01
|
|
|
|
// this week + video + over 20 minutes + HD + view count
|
|
// 08 03 12 08 08 03 10 01 18 02 20 01
|
|
|
|
//echo urlencode(urlencode(base64_encode(hex2bin($str))));
|
|
//echo bin2hex(base64_decode(urldecode(urldecode("CAI%253D"))));
|
|
|
|
// week + video + 20min + rating
|
|
// 08 01 12 06 08 03 10 01 18 02
|
|
|
|
// week + video + 20min + live + rating
|
|
// 08 01 12 08 08 03 10 01 18 02 40 01
|
|
|
|
// live 12 02 40 01
|
|
|
|
$hex = null;
|
|
if(
|
|
$date == "any" &&
|
|
$type == "video" &&
|
|
$duration == "any" &&
|
|
$feature == "any" &&
|
|
$sort == "relevance"
|
|
){
|
|
|
|
return $hex;
|
|
}
|
|
|
|
$opcode = 0;
|
|
|
|
if($date != "any"){ $opcode += 2; }
|
|
if($type != "any"){ $opcode += 2; }
|
|
if($duration != "any"){ $opcode += 2; }
|
|
|
|
switch($feature){
|
|
|
|
case "live":
|
|
case "4k":
|
|
case "hd":
|
|
case "subtitles":
|
|
case "creativecommons":
|
|
case "360":
|
|
case "3d":
|
|
$opcode += 2;
|
|
break;
|
|
|
|
case "hdr":
|
|
case "vr180":
|
|
$opcode += 3;
|
|
break;
|
|
}
|
|
|
|
switch($sort){
|
|
|
|
case "relevance": $hex .= "0800"; break;
|
|
case "upload_date": $hex .= "0802"; break;
|
|
case "view_count": $hex .= "0803"; break;
|
|
case "rating": $hex .= "0801"; break;
|
|
}
|
|
|
|
$hex .= "12" . "0".$opcode;
|
|
|
|
switch($date){
|
|
|
|
case "hour": $hex .= "0801"; break;
|
|
case "today": $hex .= "0802"; break;
|
|
case "week": $hex .= "0803"; break;
|
|
case "month": $hex .= "0804"; break;
|
|
case "year": $hex .= "0805"; break;
|
|
}
|
|
|
|
switch($type){
|
|
|
|
case "video": $hex .= "1001"; break;
|
|
case "channel": $hex .= "1002"; break;
|
|
case "playlist": $hex .= "1003"; break;
|
|
case "movie": $hex .= "1004"; break;
|
|
}
|
|
|
|
switch($duration){
|
|
|
|
case "short": $hex .= "1801"; break;
|
|
case "medium": $hex .= "1803"; break;
|
|
case "long": $hex .= "1802"; break;
|
|
}
|
|
|
|
switch($feature){
|
|
|
|
case "live": $hex .= "4001"; break;
|
|
case "4k": $hex .= "7001"; break;
|
|
case "hd": $hex .= "2001"; break;
|
|
case "subtitles": $hex .= "2801"; break;
|
|
case "creativecommons": $hex .= "3001"; break;
|
|
case "360": $hex .= "7801"; break;
|
|
case "vr180": $hex .= "d00101"; break;
|
|
case "3d": $hex .= "3801"; break;
|
|
case "hdr": $hex .= "c80101"; break;
|
|
}
|
|
|
|
//echo $hex . "\n\n";
|
|
return urlencode(base64_encode(hex2bin($hex)));
|
|
}
|
|
|
|
// me reading youtube's json
|
|
// https://imgur.com/X9hVlFX
|
|
|
|
const req_web = 0;
|
|
const req_xhr = 1;
|
|
|
|
private function get($proxy, $url, $get = [], $reqtype = self::req_web, $continuation = null){
|
|
|
|
$curlproc = curl_init();
|
|
|
|
if($get !== []){
|
|
$get = http_build_query($get);
|
|
$url .= "?" . $get;
|
|
}
|
|
|
|
curl_setopt($curlproc, CURLOPT_URL, $url);
|
|
|
|
switch($reqtype){
|
|
case self::req_web:
|
|
$headers =
|
|
["User-Agent: " . config::USER_AGENT,
|
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
"Accept-Language: en-US,en;q=0.5",
|
|
"Accept-Encoding: gzip",
|
|
"Cookie: PREF=tz=America.New_York",
|
|
"DNT: 1",
|
|
"Connection: keep-alive",
|
|
"Upgrade-Insecure-Requests: 1",
|
|
"Sec-Fetch-Dest: document",
|
|
"Sec-Fetch-Mode: navigate",
|
|
"Sec-Fetch-Site: none",
|
|
"Sec-Fetch-User: ?1"];
|
|
break;
|
|
|
|
case self::req_xhr:
|
|
$headers =
|
|
["User-Agent: " . config::USER_AGENT,
|
|
"Accept: */*",
|
|
"Accept-Language: en-US,en;q=0.5",
|
|
"Accept-Encoding: gzip",
|
|
"Cookie: PREF=tz=America.New_York",
|
|
"Referer: https://youtube.com.com/",
|
|
"Content-Type: application/json",
|
|
"Content-Length: " . strlen($continuation),
|
|
"DNT: 1",
|
|
"Connection: keep-alive",
|
|
"Sec-Fetch-Dest: empty",
|
|
"Sec-Fetch-Mode: same-origin",
|
|
"Sec-Fetch-Site: same-origin"];
|
|
|
|
curl_setopt($curlproc, CURLOPT_POST, true);
|
|
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $continuation);
|
|
break;
|
|
}
|
|
|
|
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
|
|
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
|
|
|
|
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
|
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
|
|
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
|
|
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
|
|
|
|
$this->backend->assign_proxy($curlproc, $proxy);
|
|
|
|
$data = curl_exec($curlproc);
|
|
|
|
if(curl_errno($curlproc)){
|
|
|
|
throw new Exception(curl_error($curlproc));
|
|
}
|
|
|
|
curl_close($curlproc);
|
|
return $data;
|
|
}
|
|
|
|
public function video($get){
|
|
|
|
$this->out = [
|
|
"status" => "ok",
|
|
"npt" => null,
|
|
"video" => [],
|
|
"author" => [],
|
|
"livestream" => [],
|
|
"playlist" => [],
|
|
"reel" => []
|
|
];
|
|
|
|
if($get["npt"]){
|
|
|
|
// parse nextPage
|
|
// https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false
|
|
/*
|
|
$handle = fopen("nextpage.json", "r");
|
|
$json = fread($handle, filesize("nextpage.json"));
|
|
fclose($handle);*/
|
|
|
|
[$npt, $proxy] =
|
|
$this->backend->get(
|
|
$get["npt"],
|
|
"videos"
|
|
);
|
|
|
|
$npt = json_decode($npt, true);
|
|
|
|
try{
|
|
$json = $this->get(
|
|
$proxy,
|
|
"https://www.youtube.com/youtubei/v1/search",
|
|
[
|
|
"key" => $npt["key"],
|
|
"prettyPrint" => "false"
|
|
],
|
|
self::req_xhr,
|
|
json_encode($npt["post"])
|
|
);
|
|
}catch(Exception $error){
|
|
|
|
throw new Exception("Could not fetch results page");
|
|
}
|
|
|
|
$json = json_decode($json);
|
|
|
|
foreach(
|
|
$json
|
|
->onResponseReceivedCommands[0]
|
|
->appendContinuationItemsAction
|
|
->continuationItems[0]
|
|
->itemSectionRenderer
|
|
->contents
|
|
as $video
|
|
){
|
|
|
|
$this->parsevideoobject($video);
|
|
}
|
|
|
|
if(
|
|
!isset(
|
|
$json
|
|
->onResponseReceivedCommands[0]
|
|
->appendContinuationItemsAction
|
|
->continuationItems[1]
|
|
->continuationItemRenderer
|
|
->continuationEndpoint
|
|
->continuationCommand
|
|
->token
|
|
)
|
|
){
|
|
|
|
$npt = null;
|
|
|
|
}else{
|
|
// prepare nextpage for later..
|
|
$npt["post"]["continuation"] =
|
|
$json
|
|
->onResponseReceivedCommands[0]
|
|
->appendContinuationItemsAction
|
|
->continuationItems[1]
|
|
->continuationItemRenderer
|
|
->continuationEndpoint
|
|
->continuationCommand
|
|
->token;
|
|
}
|
|
|
|
$this->out["npt"] = $npt;
|
|
|
|
}else{
|
|
|
|
$search = $get["s"];
|
|
if(strlen($search) === 0){
|
|
|
|
throw new Exception("Search term is empty!");
|
|
}
|
|
|
|
$proxy = $this->backend->get_ip();
|
|
$date = $get["date"];
|
|
$type = $get["type"];
|
|
$duration = $get["duration"];
|
|
$feature = $get["feature"];
|
|
$sort = $get["sort"];
|
|
|
|
// parse ytInitialData
|
|
|
|
$get = [
|
|
"search_query" => $search
|
|
];
|
|
|
|
if(
|
|
(
|
|
$filter =
|
|
$this->ytfilter(
|
|
$date,
|
|
$type,
|
|
$duration,
|
|
$feature,
|
|
$sort
|
|
)
|
|
) !== null
|
|
){
|
|
|
|
$get["sp"] = $filter;
|
|
}
|
|
|
|
try{
|
|
$json = $this->get(
|
|
$proxy,
|
|
"https://www.youtube.com/results",
|
|
$get
|
|
);
|
|
}catch(Exception $error){
|
|
|
|
throw new Exception("Could not fetch results page");
|
|
}
|
|
/*
|
|
$handle = fopen("test.html", "r");
|
|
$json = fread($handle, filesize("test.html"));
|
|
fclose($handle);
|
|
*/
|
|
if(
|
|
!preg_match(
|
|
'/ytcfg\.set\(({".*})\); *window\.ytcfg/',
|
|
$json,
|
|
$ytconfig
|
|
)
|
|
){
|
|
|
|
throw new Exception("Could not get ytcfg");
|
|
}
|
|
|
|
$ytconfig = json_decode($ytconfig[1]);
|
|
|
|
if(
|
|
!preg_match(
|
|
'/ytInitialData *= *({.*});<\/script>/',
|
|
$json,
|
|
$json
|
|
)
|
|
){
|
|
|
|
throw new Exception("Could not get ytInitialData");
|
|
}
|
|
|
|
$json = json_decode($json[1]);
|
|
|
|
// generate POST data for nextpage
|
|
|
|
$ytconfig->INNERTUBE_CONTEXT->client->screenWidthPoints = 1239;
|
|
$ytconfig->INNERTUBE_CONTEXT->client->screenHeightPoints = 999;
|
|
$ytconfig->INNERTUBE_CONTEXT->client->screenPixelDensity = 1;
|
|
$ytconfig->INNERTUBE_CONTEXT->client->screenDensityFloat = 1;
|
|
$ytconfig->INNERTUBE_CONTEXT->client->utcOffsetMinutes = -240;
|
|
$ytconfig->INNERTUBE_CONTEXT->request->internalExperimentFlags = [];
|
|
$ytconfig->INNERTUBE_CONTEXT->request->consistencyTokenJars = [];
|
|
|
|
$ytconfig->INNERTUBE_CONTEXT->client->mainAppWebInfo = [
|
|
"graftUrl" => $ytconfig->INNERTUBE_CONTEXT->client->originalUrl,
|
|
"webDisplayMode" => "WEB_DISPLAY_MODE_BROWSER",
|
|
"isWebNativeShareAvailable" => false
|
|
];
|
|
|
|
$ytconfig->INNERTUBE_CONTEXT->adSignalsInfo = [
|
|
"params" => [
|
|
[
|
|
"key" => "dt",
|
|
"value" => (string)$ytconfig->TIME_CREATED_MS
|
|
],
|
|
[
|
|
"key" => "flash",
|
|
"value" => "0"
|
|
],
|
|
[
|
|
"key" => "frm",
|
|
"value" => "0"
|
|
],
|
|
[
|
|
"key" => "u_tz",
|
|
"value" => "-240"
|
|
],
|
|
[
|
|
"key" => "u_his",
|
|
"value" => "3"
|
|
],
|
|
[
|
|
"key" => "u_h",
|
|
"value" => "1080"
|
|
],
|
|
[
|
|
"key" => "u_w",
|
|
"value" => "1920"
|
|
],
|
|
[
|
|
"key" => "u_ah",
|
|
"value" => "1080"
|
|
],
|
|
[
|
|
"key" => "u_cd",
|
|
"value" => "24"
|
|
],
|
|
[
|
|
"key" => "bc",
|
|
"value" => "31"
|
|
],
|
|
[
|
|
"key" => "bih",
|
|
"value" => "999"
|
|
],
|
|
[
|
|
"key" => "biw",
|
|
"value" => "1239"
|
|
],
|
|
[
|
|
"key" => "brdim",
|
|
"value" => "0,0,0,0,1920,0,1920,1061,1239,999"
|
|
],
|
|
[
|
|
"key" => "vis",
|
|
"value" => "1"
|
|
],
|
|
[
|
|
"key" => "wgl",
|
|
"value" => "true"
|
|
],
|
|
[
|
|
"key" => "ca_type",
|
|
"value" => "image"
|
|
]
|
|
]
|
|
];
|
|
|
|
/*
|
|
echo json_encode($json);
|
|
die();*/
|
|
|
|
// *inhales*
|
|
foreach(
|
|
$json
|
|
->contents
|
|
->twoColumnSearchResultsRenderer
|
|
->primaryContents
|
|
->sectionListRenderer
|
|
->contents[0]
|
|
->itemSectionRenderer
|
|
->contents
|
|
as $video
|
|
){
|
|
|
|
$this->parsevideoobject($video);
|
|
}
|
|
|
|
// get additional data from secondaryContents
|
|
if(
|
|
isset(
|
|
$json
|
|
->contents
|
|
->twoColumnSearchResultsRenderer
|
|
->secondaryContents
|
|
->secondarySearchContainerRenderer
|
|
->contents[0]
|
|
->universalWatchCardRenderer
|
|
)
|
|
){
|
|
|
|
$video =
|
|
$json
|
|
->contents
|
|
->twoColumnSearchResultsRenderer
|
|
->secondaryContents
|
|
->secondarySearchContainerRenderer
|
|
->contents[0]
|
|
->universalWatchCardRenderer;
|
|
/*
|
|
echo json_encode($video);
|
|
die();*/
|
|
|
|
$author =
|
|
[
|
|
"name" =>
|
|
$video
|
|
->header
|
|
->watchCardRichHeaderRenderer
|
|
->title
|
|
->simpleText,
|
|
"url" =>
|
|
"https://www.youtube.com/channel/" .
|
|
$video
|
|
->header
|
|
->watchCardRichHeaderRenderer
|
|
->titleNavigationEndpoint
|
|
->browseEndpoint
|
|
->browseId,
|
|
"avatar" => null
|
|
];
|
|
|
|
if(
|
|
isset(
|
|
$video
|
|
->header
|
|
->watchCardRichHeaderRenderer
|
|
->avatar
|
|
->thumbnails[0]
|
|
->url
|
|
)
|
|
){
|
|
|
|
$author["avatar"] =
|
|
$video
|
|
->header
|
|
->watchCardRichHeaderRenderer
|
|
->avatar
|
|
->thumbnails[0]
|
|
->url;
|
|
}
|
|
|
|
// add video in callToAction if present
|
|
if(
|
|
isset(
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->lengthText
|
|
)
|
|
){
|
|
|
|
array_push(
|
|
$this->out["video"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->title
|
|
->simpleText,
|
|
"description" => null,
|
|
"author" => $author,
|
|
"date" =>
|
|
$this->textualdate2unix(
|
|
trim(
|
|
explode(
|
|
"•",
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->subtitle
|
|
->simpleText
|
|
)[2]
|
|
)
|
|
),
|
|
"duration" =>
|
|
$this->hms2int(
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->lengthText
|
|
->simpleText
|
|
),
|
|
"views" =>
|
|
$this->truncatedcount2int(
|
|
trim(
|
|
explode(
|
|
"•",
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->subtitle
|
|
->simpleText,
|
|
2
|
|
)[1]
|
|
)
|
|
),
|
|
"thumb" => [
|
|
"url" =>
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->heroImage
|
|
->singleHeroImageRenderer
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url,
|
|
"ratio" => "16:9"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$video
|
|
->callToAction
|
|
->watchCardHeroVideoRenderer
|
|
->navigationEndpoint
|
|
->watchEndpoint
|
|
->videoId
|
|
]
|
|
);
|
|
}
|
|
|
|
// get all playlists, ignore videos
|
|
$out = null;
|
|
|
|
foreach(
|
|
$video
|
|
->sections
|
|
as $section
|
|
){
|
|
|
|
if(
|
|
isset(
|
|
$section
|
|
->watchCardSectionSequenceRenderer
|
|
->lists[0]
|
|
->horizontalCardListRenderer
|
|
->cards
|
|
)
|
|
){
|
|
|
|
$out =
|
|
$section
|
|
->watchCardSectionSequenceRenderer
|
|
->lists[0]
|
|
->horizontalCardListRenderer
|
|
->cards;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if($out !== null){
|
|
|
|
foreach(
|
|
$out as $video
|
|
){
|
|
|
|
if(
|
|
!isset(
|
|
$video
|
|
->searchRefinementCardRenderer
|
|
)
|
|
){
|
|
|
|
continue;
|
|
}
|
|
|
|
$video =
|
|
$video
|
|
->searchRefinementCardRenderer;
|
|
|
|
array_push(
|
|
$this->out["playlist"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->query
|
|
->runs[0]
|
|
->text,
|
|
"description" => null,
|
|
"author" => $author,
|
|
"date" => null,
|
|
"duration" => null,
|
|
"views" => null,
|
|
"thumb" => [
|
|
"url" =>
|
|
$video
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url,
|
|
"ratio" => "1:1"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com" .
|
|
$video
|
|
->searchEndpoint
|
|
->commandMetadata
|
|
->webCommandMetadata
|
|
->url
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach(
|
|
$json
|
|
->contents
|
|
->twoColumnSearchResultsRenderer
|
|
->primaryContents
|
|
->sectionListRenderer
|
|
->contents
|
|
as $cont
|
|
){
|
|
|
|
if(isset($cont->continuationItemRenderer)){
|
|
|
|
$this->out["npt"] = [
|
|
"key" =>
|
|
$ytconfig
|
|
->INNERTUBE_API_KEY,
|
|
"post" => [
|
|
"context" =>
|
|
$ytconfig
|
|
->INNERTUBE_CONTEXT,
|
|
"continuation" =>
|
|
$cont
|
|
->continuationItemRenderer
|
|
->continuationEndpoint
|
|
->continuationCommand
|
|
->token
|
|
]
|
|
];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if($this->out["npt"] !== null){
|
|
|
|
$this->out["npt"] =
|
|
$this->backend->store(
|
|
json_encode(
|
|
$this->out["npt"]
|
|
),
|
|
"videos",
|
|
$proxy
|
|
);
|
|
}
|
|
|
|
return $this->out;
|
|
}
|
|
|
|
private function parsevideoobject($video){
|
|
|
|
if(isset($video->videoRenderer)){
|
|
|
|
$video = $video->videoRenderer;
|
|
|
|
$description = null;
|
|
|
|
if(isset($video->detailedMetadataSnippets)){
|
|
foreach(
|
|
$video
|
|
->detailedMetadataSnippets[0]
|
|
->snippetText
|
|
->runs
|
|
as $description_part
|
|
){
|
|
|
|
$description .= $description_part->text;
|
|
}
|
|
}
|
|
|
|
if(
|
|
isset(
|
|
$video
|
|
->badges[0]
|
|
->metadataBadgeRenderer
|
|
->icon
|
|
->iconType
|
|
) &&
|
|
$video
|
|
->badges[0]
|
|
->metadataBadgeRenderer
|
|
->icon
|
|
->iconType
|
|
== "LIVE"
|
|
){
|
|
|
|
$type = "livestream";
|
|
$date = null;
|
|
$duration = "_LIVE";
|
|
|
|
if(isset($video->viewCountText->runs[0]->text)){
|
|
|
|
$views =
|
|
$this->views2int(
|
|
$video
|
|
->viewCountText
|
|
->runs[0]
|
|
->text
|
|
);
|
|
}else{
|
|
|
|
$views = null;
|
|
}
|
|
}else{
|
|
|
|
$type = "video";
|
|
|
|
if(isset($video->publishedTimeText->simpleText)){
|
|
|
|
$date = $this->textualdate2unix(
|
|
$video
|
|
->publishedTimeText
|
|
->simpleText
|
|
);
|
|
}else{
|
|
|
|
$date = null;
|
|
}
|
|
|
|
if(isset($video->lengthText->simpleText)){
|
|
|
|
$duration =
|
|
$this->hms2int(
|
|
$video
|
|
->lengthText
|
|
->simpleText
|
|
);
|
|
}else{
|
|
|
|
$duration = null;
|
|
}
|
|
|
|
if(isset($video->viewCountText->simpleText)){
|
|
|
|
$views =
|
|
$this->views2int(
|
|
$video
|
|
->viewCountText
|
|
->simpleText
|
|
);
|
|
}else{
|
|
|
|
$views = null;
|
|
}
|
|
}
|
|
|
|
if(
|
|
$video
|
|
->navigationEndpoint
|
|
->commandMetadata
|
|
->webCommandMetadata
|
|
->webPageType
|
|
== "WEB_PAGE_TYPE_SHORTS"
|
|
){
|
|
|
|
// haha you thought you could get me, youtube
|
|
// jokes on you i dont go outside
|
|
$type = "reel";
|
|
}
|
|
|
|
array_push(
|
|
$this->out[$type],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->title
|
|
->runs[0]
|
|
->text,
|
|
"description" =>
|
|
$this->titledots($description),
|
|
"author" => [
|
|
"name" =>
|
|
$video
|
|
->longBylineText
|
|
->runs[0]
|
|
->text,
|
|
"url" =>
|
|
"https://www.youtube.com/channel/" .
|
|
$video
|
|
->longBylineText
|
|
->runs[0]
|
|
->navigationEndpoint
|
|
->browseEndpoint
|
|
->browseId,
|
|
"avatar" =>
|
|
$this->checkhttpspresence(
|
|
$video
|
|
->channelThumbnailSupportedRenderers
|
|
->channelThumbnailWithLinkRenderer
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url
|
|
)
|
|
],
|
|
"date" => $date,
|
|
"duration" => $duration,
|
|
"views" => $views,
|
|
"thumb" => [
|
|
"url" =>
|
|
$video
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url,
|
|
"ratio" => "16:9"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$video
|
|
->videoId
|
|
]
|
|
);
|
|
}elseif(isset($video->watchCardCompactVideoRenderer)){
|
|
|
|
$video =
|
|
$video
|
|
->watchCardCompactVideoRenderer;
|
|
|
|
array_push(
|
|
$this->out["video"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->title
|
|
->simpleText,
|
|
"description" => null,
|
|
"author" => [
|
|
"name" =>
|
|
$video
|
|
->byline
|
|
->runs[0]
|
|
->text,
|
|
"url" =>
|
|
"https://www.youtube.com/channel/" .
|
|
$video
|
|
->byline
|
|
->runs[0]
|
|
->navigationEndpoint
|
|
->browseEndpoint
|
|
->browseId,
|
|
"avatar" => null
|
|
],
|
|
"date" =>
|
|
$this->textualdate2unix(
|
|
trim(
|
|
explode(
|
|
"•",
|
|
$video
|
|
->subtitle
|
|
->simpleText,
|
|
2
|
|
)[1]
|
|
)
|
|
),
|
|
"duration" =>
|
|
$this->hms2int(
|
|
$video
|
|
->lengthText
|
|
->simpleText
|
|
),
|
|
"views" =>
|
|
$this->truncatedcount2int(
|
|
trim(
|
|
explode(
|
|
"•",
|
|
$video
|
|
->subtitle
|
|
->simpleText,
|
|
2
|
|
)[0]
|
|
)
|
|
),
|
|
"thumb" => [
|
|
"url" =>
|
|
$video
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url,
|
|
"ratio" => "16:9"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$video
|
|
->navigationEndpoint
|
|
->watchEndpoint
|
|
->videoId
|
|
]
|
|
);
|
|
|
|
}elseif(isset($video->reelShelfRenderer)){
|
|
|
|
foreach(
|
|
$video
|
|
->reelShelfRenderer
|
|
->items
|
|
as $reel
|
|
){
|
|
|
|
$reel =
|
|
$reel
|
|
->reelItemRenderer;
|
|
|
|
array_push(
|
|
$this->out["reel"],
|
|
[
|
|
"title" =>
|
|
$reel
|
|
->headline
|
|
->simpleText,
|
|
"description" => null,
|
|
"author" => [
|
|
"name" => null,
|
|
"url" => null,
|
|
"avatar" => null
|
|
],
|
|
"date" => null,
|
|
"duration" =>
|
|
$this->textualtime2int(
|
|
$reel
|
|
->accessibility
|
|
->accessibilityData
|
|
->label
|
|
),
|
|
"views" =>
|
|
$this->truncatedcount2int(
|
|
$reel
|
|
->viewCountText
|
|
->simpleText
|
|
),
|
|
"thumb" => [
|
|
"url" =>
|
|
$reel
|
|
->thumbnail
|
|
->thumbnails[0]
|
|
->url,
|
|
"ratio" => "9:16"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$reel
|
|
->videoId
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
elseif(isset($video->channelRenderer)){
|
|
|
|
$video = $video->channelRenderer;
|
|
|
|
$description = null;
|
|
|
|
if(isset($video->descriptionSnippet)){
|
|
|
|
foreach(
|
|
$video
|
|
->descriptionSnippet
|
|
->runs
|
|
as $description_part
|
|
){
|
|
|
|
$description .= $description_part->text;
|
|
}
|
|
}
|
|
|
|
array_push(
|
|
$this->out["author"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->title
|
|
->simpleText,
|
|
"followers" =>
|
|
isset(
|
|
$video
|
|
->videoCountText
|
|
->simpleText
|
|
) ?
|
|
$this->truncatedcount2int(
|
|
$video
|
|
->videoCountText
|
|
->simpleText
|
|
) :
|
|
0,
|
|
"description" => $this->titledots($description),
|
|
"thumb" =>
|
|
[
|
|
"url" =>
|
|
$this->checkhttpspresence(
|
|
$video
|
|
->thumbnail
|
|
->thumbnails[
|
|
count(
|
|
$video
|
|
->thumbnail
|
|
->thumbnails
|
|
) - 1
|
|
]
|
|
->url
|
|
),
|
|
"ratio" => "1:1"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/channel/" .
|
|
$video
|
|
->channelId
|
|
]
|
|
);
|
|
}
|
|
|
|
elseif(isset($video->shelfRenderer)){
|
|
|
|
if(
|
|
!is_object(
|
|
$video
|
|
->shelfRenderer
|
|
->content
|
|
->verticalListRenderer
|
|
)
|
|
){
|
|
return;
|
|
}
|
|
|
|
foreach(
|
|
$video
|
|
->shelfRenderer
|
|
->content
|
|
->verticalListRenderer
|
|
->items
|
|
as $shelfvideo
|
|
){
|
|
|
|
$this->parsevideoobject($shelfvideo);
|
|
}
|
|
|
|
}elseif(isset($video->radioRenderer)){
|
|
|
|
$video = $video->radioRenderer;
|
|
|
|
$description =
|
|
$video
|
|
->videoCountText
|
|
->runs[0]
|
|
->text
|
|
. ".";
|
|
|
|
$tmp = [];
|
|
foreach(
|
|
$video->videos
|
|
as $childvideo
|
|
){
|
|
|
|
$tmp[] =
|
|
$childvideo
|
|
->childVideoRenderer
|
|
->title
|
|
->simpleText;
|
|
}
|
|
|
|
if(count($tmp) !== 0){
|
|
|
|
$description .=
|
|
" " . implode(", ", $tmp);
|
|
}
|
|
|
|
array_push(
|
|
$this->out["playlist"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->title
|
|
->simpleText,
|
|
"description" => $description,
|
|
"author" => [
|
|
"name" =>
|
|
$video
|
|
->longBylineText
|
|
->simpleText,
|
|
"url" => null,
|
|
"avatar" => null
|
|
],
|
|
"date" => null,
|
|
"duration" => null,
|
|
"views" => null,
|
|
"thumb" => [
|
|
"url" =>
|
|
$video
|
|
->thumbnail
|
|
->thumbnails[
|
|
count(
|
|
$video
|
|
->thumbnail
|
|
->thumbnails
|
|
) - 1
|
|
]
|
|
->url,
|
|
"ratio" => "16:9"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$video
|
|
->videos[0]
|
|
->childVideoRenderer
|
|
->videoId .
|
|
"&list=" .
|
|
$video
|
|
->playlistId .
|
|
"&start_radio=1"
|
|
]
|
|
);
|
|
|
|
}elseif(isset($video->playlistRenderer)){
|
|
|
|
$video = $video->playlistRenderer;
|
|
|
|
$description = $video->videoCount . " videos.";
|
|
|
|
$tmp = [];
|
|
foreach(
|
|
$video
|
|
->videos
|
|
as $childvideo
|
|
){
|
|
|
|
$tmp[] =
|
|
$childvideo
|
|
->childVideoRenderer
|
|
->title
|
|
->simpleText;
|
|
}
|
|
|
|
if(count($tmp) !== 0){
|
|
|
|
$description .=
|
|
" " . implode(", ", $tmp);
|
|
}
|
|
|
|
array_push(
|
|
$this->out["playlist"],
|
|
[
|
|
"title" =>
|
|
$video
|
|
->title
|
|
->simpleText,
|
|
"description" => $description,
|
|
"author" => [
|
|
"name" =>
|
|
$video
|
|
->longBylineText
|
|
->runs[0]
|
|
->text,
|
|
"url" =>
|
|
"https://www.youtube.com/channel/" .
|
|
$video
|
|
->longBylineText
|
|
->runs[0]
|
|
->navigationEndpoint
|
|
->browseEndpoint
|
|
->browseId,
|
|
"picture" => null
|
|
],
|
|
"date" => null,
|
|
"duration" => null,
|
|
"views" => null,
|
|
"thumb" =>
|
|
[
|
|
"url" =>
|
|
$video
|
|
->thumbnails[0]
|
|
->thumbnails[
|
|
count(
|
|
$video
|
|
->thumbnails[0]
|
|
->thumbnails
|
|
) - 1
|
|
]
|
|
->url,
|
|
"ratio" => "16:9"
|
|
],
|
|
"url" =>
|
|
"https://www.youtube.com/watch?v=" .
|
|
$video
|
|
->videos[0]
|
|
->childVideoRenderer
|
|
->videoId .
|
|
"&list=" .
|
|
$video
|
|
->playlistId .
|
|
"&start_radio=1"
|
|
]
|
|
);
|
|
|
|
}/*else{
|
|
if(!isset($video->searchPyvRenderer)){
|
|
echo json_encode($video);
|
|
die();}
|
|
}*/
|
|
}
|
|
|
|
private function textualdate2unix($number){
|
|
|
|
$number =
|
|
explode(
|
|
" ",
|
|
str_replace(
|
|
[
|
|
" ago",
|
|
"seconds",
|
|
"minutes",
|
|
"hours",
|
|
"days",
|
|
"weeks",
|
|
"months",
|
|
"years"
|
|
],
|
|
[
|
|
"",
|
|
"second",
|
|
"minute",
|
|
"hour",
|
|
"day",
|
|
"week",
|
|
"month",
|
|
"year"
|
|
],
|
|
$number
|
|
),
|
|
2
|
|
);
|
|
|
|
$time = 0;
|
|
switch($number[1]){
|
|
|
|
case "second":
|
|
$time = (int)$number[0];
|
|
break;
|
|
|
|
case "minute":
|
|
$time = (int)$number[0] * 60;
|
|
break;
|
|
|
|
case "hour":
|
|
$time = (int)$number[0] * 3600;
|
|
break;
|
|
|
|
case "day":
|
|
$time = (int)$number[0] * 86400;
|
|
break;
|
|
|
|
case "week":
|
|
$time = (int)$number[0] * 604800;
|
|
break;
|
|
|
|
case "month":
|
|
$time = (int)$number[0] * 2629746;
|
|
break;
|
|
|
|
case "year":
|
|
$time = (int)$number[0] * 31556952;
|
|
break;
|
|
}
|
|
|
|
return time() - $time;
|
|
}
|
|
|
|
private function checkhttpspresence($link){
|
|
|
|
if(substr($link, 0, 2) == "//"){
|
|
|
|
return "https:" . $link;
|
|
}
|
|
|
|
return $link;
|
|
}
|
|
|
|
private function textualtime2int($number){
|
|
|
|
$number = explode(" - ", $number);
|
|
|
|
if(count($number) >= 2){
|
|
|
|
$number = $number[count($number) - 2];
|
|
}else{
|
|
|
|
$number = $number[0];
|
|
}
|
|
|
|
$number =
|
|
str_replace(
|
|
[
|
|
" ",
|
|
"seconds",
|
|
"minutes",
|
|
"hours",
|
|
],
|
|
[
|
|
"",
|
|
"second",
|
|
"minute",
|
|
"hour"
|
|
],
|
|
$number
|
|
);
|
|
|
|
preg_match_all(
|
|
'/([0-9]+)(second|minute|hour)/',
|
|
$number,
|
|
$number
|
|
);
|
|
|
|
$time = 0;
|
|
|
|
for($i=0; $i<count($number[0]); $i++){
|
|
|
|
switch($number[2][$i]){
|
|
|
|
case "second":
|
|
$time = $time + (int)$number[1][$i];
|
|
break;
|
|
|
|
case "minute":
|
|
$time = $time + ((int)$number[1][$i] * 60);
|
|
break;
|
|
|
|
case "hour":
|
|
$time = $time + ((int)$number[1][$i] * 3600);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $time;
|
|
}
|
|
|
|
private function views2int($views){
|
|
|
|
return
|
|
(int)str_replace(
|
|
",", "",
|
|
explode(" ", $views, 2)[0]
|
|
);
|
|
}
|
|
|
|
private function hms2int($time){
|
|
|
|
$parts = explode(":", $time, 3);
|
|
$time = 0;
|
|
|
|
if(count($parts) === 3){
|
|
|
|
// hours
|
|
$time = $time + ((int)$parts[0] * 3600);
|
|
array_shift($parts);
|
|
}
|
|
|
|
if(count($parts) === 2){
|
|
|
|
// minutes
|
|
$time = $time + ((int)$parts[0] * 60);
|
|
array_shift($parts);
|
|
}
|
|
|
|
// seconds
|
|
$time = $time + (int)$parts[0];
|
|
|
|
return $time;
|
|
}
|
|
|
|
private function truncatedcount2int($number){
|
|
|
|
// decimal should always be 1 number long
|
|
$number = explode(" ", $number, 2);
|
|
$number = $number[0];
|
|
|
|
$unit = strtolower($number[strlen($number) - 1]);
|
|
|
|
$tmp = explode(".", $number, 2);
|
|
$number = (int)$number;
|
|
|
|
if(count($tmp) === 2){
|
|
|
|
$decimal = (int)$tmp[1];
|
|
}else{
|
|
|
|
$decimal = 0;
|
|
}
|
|
|
|
switch($unit){
|
|
|
|
case "k":
|
|
$exponant = 1000;
|
|
break;
|
|
|
|
case "m":
|
|
$exponant = 1000000;
|
|
break;
|
|
|
|
case "b";
|
|
$exponant = 1000000000;
|
|
break;
|
|
|
|
default:
|
|
$exponant = 1;
|
|
break;
|
|
}
|
|
|
|
return ($number * $exponant) + ($decimal * ($exponant / 10));
|
|
}
|
|
|
|
private function titledots($title){
|
|
|
|
$substr = substr($title, -3);
|
|
|
|
if(
|
|
$substr == "..." ||
|
|
$substr == "…"
|
|
){
|
|
|
|
return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
|
|
}
|
|
|
|
return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
|
|
}
|
|
}
|