Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YoutubeBridge] Several fixes and switch maintainer #2115

Merged
merged 5 commits into from
May 16, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 78 additions & 20 deletions bridges/YoutubeBridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class YoutubeBridge extends BridgeAbstract {
const URI = 'https://www.youtube.com/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the 10 newest videos by username/channel/playlist or search';
const MAINTAINER = 'mitsukarenai';
const MAINTAINER = 'em92';

const PARAMETERS = array(
'By username' => array(
Expand Down Expand Up @@ -68,29 +68,35 @@ private function ytBridgeQueryVideoInfo($vid, &$author, &$desc, &$time){
$html = $this->ytGetSimpleHTMLDOM(self::URI . "watch?v=$vid", true);

// Skip unavailable videos
if(!strpos($html->innertext, 'IS_UNAVAILABLE_PAGE')) {
if(strpos($html->innertext, 'IS_UNAVAILABLE_PAGE') !== false) {
return;
}

foreach($html->find('script') as $script) {
$data = trim($script->innertext);

if(strpos($data, '{') !== 0)
continue; // Wrong script
$elAuthor = $html->find('span[itemprop=author] > link[itemprop=name]', 0);
if (!is_null($elAuthor)) {
$author = $elAuthor->getAttribute('content');
}

$json = json_decode($data);
$elDatePublished = $html->find('meta[itemprop=datePublished]', 0);
if(!is_null($elDatePublished))
$time = strtotime($elDatePublished->getAttribute('content'));

if(!isset($json->itemListElement))
continue; // Wrong script
$scriptRegex = '/var ytInitialData = (.*);<\/script>/';
preg_match($scriptRegex, $html, $matches) or returnServerError('Could not find ytInitialData');
$jsonData = json_decode($matches[1]);
$jsonData = $jsonData->contents->twoColumnWatchNextResults->results->results->contents;

$author = $json->itemListElement[0]->item->name;
$videoSecondaryInfo = null;
foreach($jsonData as $item) {
if (isset($item->videoSecondaryInfoRenderer)) {
$videoSecondaryInfo = $item->videoSecondaryInfoRenderer;
break;
}
}

if(!is_null($html->find('#watch-description-text', 0)))
$desc = $html->find('#watch-description-text', 0)->innertext;

if(!is_null($html->find('meta[itemprop=datePublished]', 0)))
$time = strtotime($html->find('meta[itemprop=datePublished]', 0)->getAttribute('content'));
if (!$videoSecondaryInfo) {
returnServerError('Could not find videoSecondaryInfoRenderer');
}
$desc = nl2br($videoSecondaryInfo->description->runs[0]->text);
}

private function ytBridgeAddItem($vid, $title, $author, $desc, $time){
Expand Down Expand Up @@ -241,16 +247,28 @@ public function collectData(){
returnServerError("Could not request YouTube. Tried:\n - $url_feed\n - $url_listing");
}
} elseif($this->getInput('p')) { /* playlist mode */
// TODO: this mode makes a lot of excess video query requests.
// To make less requests, we need to cache following dictionary "videoId -> datePublished, duration"
// This cache will be used to find out, which videos to fetch
// to make feed of 15 items or more, if there a lot of videos published on that date.
$this->request = $this->getInput('p');
$url_feed = self::URI . 'feeds/videos.xml?playlist_id=' . urlencode($this->request);
$url_listing = self::URI . 'playlist?list=' . urlencode($this->request);
$html = $this->ytGetSimpleHTMLDOM($url_listing)
or returnServerError("Could not request YouTube. Tried:\n - $url_listing");
$item_count = $this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a', false);
$scriptRegex = '/var ytInitialData = (.*);<\/script>/';
preg_match($scriptRegex, $html, $matches) or returnServerError('Could not find ytInitialData');
// TODO: this method returns only first 100 video items
// if it has more videos, playlistVideoListRenderer will have continuationItemRenderer as last element
$jsonData = json_decode($matches[1]);
$jsonData = $jsonData->contents->twoColumnBrowseResultsRenderer->tabs[0];
$jsonData = $jsonData->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer;
$jsonData = $jsonData->contents[0]->playlistVideoListRenderer->contents;
$item_count = count($jsonData);
if ($item_count <= 15 && !$this->skipFeeds() && ($xml = $this->ytGetSimpleHTMLDOM($url_feed))) {
$this->ytBridgeParseXmlFeed($xml);
} else {
$this->ytBridgeParseHtmlListing($html, 'tr.pl-video', '.pl-video-title a');
$this->parseJsonPlaylist($jsonData);
}
$this->feedName = 'Playlist: ' . str_replace(' - YouTube', '', $html->find('title', 0)->plaintext); // feedName will be used by getName()
usort($this->items, function ($item1, $item2) {
Expand Down Expand Up @@ -284,16 +302,56 @@ private function skipFeeds() {
return ($this->getInput('duration_min') || $this->getInput('duration_max'));
}

public function getURI()
{
if (!is_null($this->getInput('p'))) {
return static::URI . 'playlist?list=' . $this->getInput('p');
}

return parent::getURI();
}

public function getName(){
// Name depends on queriedContext:
switch($this->queriedContext) {
case 'By username':
case 'By channel id':
case 'By playlist Id':
case 'Search result':
return $this->feedName . ' - YouTube'; // We already know it's a bridge, right?
return htmlspecialchars_decode($this->feedName) . ' - YouTube'; // We already know it's a bridge, right?
default:
return parent::getName();
}
}

private function parseJsonPlaylist($jsonData) {
$duration_min = $this->getInput('duration_min') ?: -1;
$duration_min = $duration_min * 60;

$duration_max = $this->getInput('duration_max') ?: INF;
$duration_max = $duration_max * 60;

if($duration_max < $duration_min) {
returnClientError('Max duration must be greater than min duration!');
}

foreach($jsonData as $item) {
if (!isset($item->playlistVideoRenderer)) {
continue;
}
$vid = $item->playlistVideoRenderer->videoId;
$title = $item->playlistVideoRenderer->title->runs[0]->text;

$author = '';
$desc = '';
$time = 0;
$duration = intval($item->playlistVideoRenderer->lengthSeconds);
if($duration < $duration_min || $duration > $duration_max) {
continue;
}

$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
}
}
}