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

[YouTubeCommunityTabBridge] Add Bridge #1594

Merged
merged 20 commits into from
Mar 25, 2022
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8027209
[YouTubeCommunityTabBridge] Add Bridge
VerifiedJoseph Jun 8, 2020
7fe51c1
[YouTubeCommunityTabBridge] Use end() instead of array_key_last()
VerifiedJoseph Jun 8, 2020
203722a
[YouTubeCommunityTabBridge] Break loop if 'messageRenderer' is given
VerifiedJoseph Jun 8, 2020
0c5dbe0
Remove whitespace
VerifiedJoseph Jun 8, 2020
8487efe
[YouTubeCommunityTabBridge] Fix JSON extraction
VerifiedJoseph Aug 29, 2020
13e3f3b
[YouTubeCommunityTabBridge] Update comment
VerifiedJoseph Aug 29, 2020
fe9186e
[YouTubeCommunityTabBridge] Fix JSON extraction
VerifiedJoseph Dec 28, 2020
a922900
[YouTubeCommunityTabBridge] Fix item author
VerifiedJoseph Dec 28, 2020
9c37289
[YouTubeCommunityTabBridge] Add detectParameters()
VerifiedJoseph Feb 24, 2021
8d3442c
[YouTubeCommunityTabBridge] Remove whitespace
VerifiedJoseph Feb 24, 2021
4f46651
[YouTubeCommunityTabBridge] Fix text extraction
VerifiedJoseph Feb 28, 2021
b273ec5
[YouTubeCommunityTabBridge] Move formatUrls()
VerifiedJoseph Feb 28, 2021
75b6b6b
[YouTubeCommunityTabBridge] Remove whitespace
VerifiedJoseph Feb 28, 2021
8015187
[YouTubeCommunityTabBridge] Skip items missing 'backstagePostThreadRe…
VerifiedJoseph Mar 10, 2021
94c8caa
[YouTubeCommunityTabBridge] Add support username URLs
VerifiedJoseph Mar 10, 2021
b65f229
[YouTubeCommunityTabBridge] Remove use of defaultLinkTo()
VerifiedJoseph Mar 10, 2021
3126dd7
[YouTubeCommunityTabBridge] Fix data extraction
VerifiedJoseph Aug 7, 2021
e0ff43f
[YouTubeCommunityTabBridge] Fix poll totalVotes
VerifiedJoseph Aug 7, 2021
3abcae5
Add support for usernames from youtube.com/c/ URLs
VerifiedJoseph Mar 25, 2022
ae4a14e
[YouTubeCommunityTabBridge] Run phpcbf
VerifiedJoseph Mar 25, 2022
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
267 changes: 267 additions & 0 deletions bridges/YouTubeCommunityTabBridge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<?php
class YouTubeCommunityTabBridge extends BridgeAbstract {
const NAME = 'YouTube Community Tab Bridge';
const URI = 'https://www.youtube.com';
const DESCRIPTION = 'Returns posts from a channel\'s community tab';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'By channel ID' => array(
'channel' => array(
'name' => 'Channel ID',
'type' => 'text',
'required' => true,
'exampleValue' => 'UCULkRHBdLC5ZcEQBaL0oYHQ'
)
),
'By username' => array(
'username' => array(
'name' => 'Username',
'type' => 'text',
'required' => true,
'exampleValue' => 'YouTubeUK'
),
)
);

const CACHE_TIMEOUT = 3600; // 1 hour

private $feedUrl = '';
private $feedName = '';
private $itemTitle = '';

private $urlRegex = '/youtube\.com\/(channel|user|c)\/([\w]+)\/community/';
private $jsonRegex = '/var ytInitialData = (.*);<\/script><link rel="canonical"/';

public function detectParameters($url) {
$params = array();

if(preg_match($this->urlRegex, $url, $matches)) {
if ($matches[1] === 'channel') {
$params['context'] = 'By channel ID';
$params['channel'] = $matches[2];
}

if ($matches[1] === 'user') {
$params['context'] = 'By username';
$params['username'] = $matches[2];
}

return $params;
}

return null;
}

public function collectData() {

if (is_null($this->getInput('username')) === false) {
try {
$this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'c');
$html = getSimpleHTMLDOM($this->feedUrl);

} catch (Exception $e) {
$this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'user');
$html = getSimpleHTMLDOM($this->feedUrl);
}
} else {
$this->feedUrl = $this->buildCommunityUri($this->getInput('channel'), 'channel');
$html = getSimpleHTMLDOM($this->feedUrl);
}

$json = $this->extractJson($html->find('body', 0)->innertext);

$this->feedName = $json->header->c4TabbedHeaderRenderer->title;

if ($this->hasCommunityTab($json) === false) {
returnServerError('Channel does not have a community tab');
}

foreach ($this->getCommunityPosts($json) as $post) {
$this->itemTitle = '';

if (!isset($post->backstagePostThreadRenderer)) {
continue;
}

$details = $post->backstagePostThreadRenderer->post->backstagePostRenderer;

$item = array();
$item['uri'] = self::URI . '/post/' . $details->postId;
$item['author'] = $details->authorText->runs[0]->text;
$item['content'] = '';

if (isset($details->contentText)) {
$text = $this->getText($details->contentText->runs);

$this->itemTitle = $this->ellipsisTitle($text);
$item['content'] = $text;
}

$item['content'] .= $this->getAttachments($details);
$item['title'] = $this->itemTitle;

$this->items[] = $item;
}
}

public function getURI() {

if (!empty($this->feedUri)) {
return $this->feedUri;
}

return parent::getURI();
}

public function getName() {

if (!empty($this->feedName)) {
return $this->feedName . ' - YouTube Community Tab';
}

return parent::getName();
}

/**
* Build Community URI
*/
private function buildCommunityUri($value, $type) {
return self::URI . '/' . $type . '/' . $value . '/community';
}

/**
* Extract JSON from page
*/
private function extractJson($html) {

if (!preg_match($this->jsonRegex, $html, $parts)) {
returnServerError('Failed to extract data from page');
}

$data = json_decode($parts[1]);

if ($data === false) {
returnServerError('Failed to decode extracted data');
}

return $data;
}

/**
* Check if channel has a community tab
*/
private function hasCommunityTab($json) {

foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) {
if (isset($tab->tabRenderer) && $tab->tabRenderer->title === 'Community') {
return true;
}
}

return false;
}

/**
* Get community tab posts
*/
private function getCommunityPosts($json) {

foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) {
if ($tab->tabRenderer->title === 'Community') {
return $tab->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer->contents;
}
}
}

/**
* Get text content for a post
*/
private function getText($runs) {
$text = '';

foreach ($runs as $part) {
$text .= $this->formatUrls($part->text);
}

return nl2br($text);
}

/**
* Get attachments for posts
*/
private function getAttachments($details) {
$content = '';

if (isset($details->backstageAttachment)) {
$attachments = $details->backstageAttachment;

// Video
if (isset($attachments->videoRenderer) && isset($attachments->videoRenderer->videoId)) {
if (empty($this->itemTitle)) {
$this->itemTitle = $this->feedName . ' posted a video';
}

$content = <<<EOD
<iframe width="100%" height="410" src="https://www.youtube.com/embed/{$attachments->videoRenderer->videoId}"
frameborder="0" allow="encrypted-media;" allowfullscreen></iframe>
EOD;
}

// Image
if (isset($attachments->backstageImageRenderer)) {
if (empty($this->itemTitle)) {
$this->itemTitle = $this->feedName . ' posted an image';
}

$lastThumb = end($attachments->backstageImageRenderer->image->thumbnails);

$content = <<<EOD
<p><img src="{$lastThumb->url}"></p>
EOD;
}

// Poll
if (isset($attachments->pollRenderer)) {
if (empty($this->itemTitle)) {
$this->itemTitle = $this->feedName . ' posted a poll';
}

$pollChoices = '';

foreach ($attachments->pollRenderer->choices as $choice) {
$pollChoices .= <<<EOD
<li>{$choice->text->runs[0]->text}</li>
EOD;
}

$content = <<<EOD
<hr><p>Poll ({$attachments->pollRenderer->totalVotes->simpleText})<br><ul>{$pollChoices}</ul><p>
EOD;
}
}

return $content;
}

/*
Ellipsis text for title
*/
private function ellipsisTitle($text) {
$length = 100;

if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}

return $text;
}

private function formatUrls($content) {
return preg_replace(
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
'<a target="_blank" href="$1" target="_blank">$1</a> ',
$content
);
}
}