Add a full-pane version of youtube extension, complete with fixing the extra duplicate thumbnail bug in freshrss
Some checks are pending
Automated tests / tests (push) Waiting to run
Some checks are pending
Automated tests / tests (push) Waiting to run
This commit is contained in:
parent
361a428139
commit
2cc2e8aab2
10 changed files with 1109 additions and 0 deletions
267
xExtension-YouTube-wide/extension.php
Normal file
267
xExtension-YouTube-wide/extension.php
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class YouTubeWideExtension
|
||||
*
|
||||
* Latest version can be found at https://github.com/kevinpapst/freshrss-youtube
|
||||
*
|
||||
* @author Kevin Papst
|
||||
*/
|
||||
final class YouTubeWideExtension extends Minz_Extension
|
||||
{
|
||||
/**
|
||||
* Video player width
|
||||
*/
|
||||
private string $width = '100%';
|
||||
/**
|
||||
* Video player height
|
||||
*/
|
||||
private string $height = '';
|
||||
/**
|
||||
* Whether we display the original feed content
|
||||
*/
|
||||
private bool $showContent = false;
|
||||
/**
|
||||
* Switch to enable the Youtube No-Cookie domain
|
||||
*/
|
||||
private bool $useNoCookie = false;
|
||||
|
||||
/**
|
||||
* Initialize this extension
|
||||
*/
|
||||
#[\Override]
|
||||
public function init(): void
|
||||
{
|
||||
Minz_View::appendStyle($this->getFileUrl('style.css', 'css'));
|
||||
$this->registerHook('entry_before_display', [$this, 'embedYouTubeVideo']);
|
||||
$this->registerHook('check_url_before_add', [self::class, 'convertYoutubeFeedUrl']);
|
||||
$this->registerTranslates();
|
||||
}
|
||||
|
||||
public static function convertYoutubeFeedUrl(string $url): string
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('#^https?://www\.youtube\.com/channel/([0-9a-zA-Z_-]{6,36})#', $url, $matches) === 1) {
|
||||
return 'https://www.youtube.com/feeds/videos.xml?channel_id=' . $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match('#^https?://www\.youtube\.com/user/([0-9a-zA-Z_-]{6,36})#', $url, $matches) === 1) {
|
||||
return 'https://www.youtube.com/feeds/videos.xml?user=' . $matches[1];
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the extension configuration, if the user context is available.
|
||||
* Do not call that in your extensions init() method, it can't be used there.
|
||||
* @throws FreshRSS_Context_Exception
|
||||
*/
|
||||
public function loadConfigValues(): void
|
||||
{
|
||||
if (!class_exists('FreshRSS_Context', false) || !FreshRSS_Context::hasUserConf()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$width = FreshRSS_Context::userConf()->attributeString('ytw_width');
|
||||
if ($width !== null) {
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
$height = FreshRSS_Context::userConf()->attributeString('ytw_height');
|
||||
if ($height !== null) {
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
$showContent = FreshRSS_Context::userConf()->attributeBool('ytw_show_content');
|
||||
if ($showContent !== null) {
|
||||
$this->showContent = $showContent;
|
||||
}
|
||||
|
||||
$noCookie = FreshRSS_Context::userConf()->attributeBool('ytw_nocookie');
|
||||
if ($noCookie !== null) {
|
||||
$this->useNoCookie = $noCookie;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width in pixel for the YouTube player iframe.
|
||||
* You have to call loadConfigValues() before this one, otherwise you get default values.
|
||||
*/
|
||||
public function getWidth(): string
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height in pixel for the YouTube player iframe.
|
||||
* You have to call loadConfigValues() before this one, otherwise you get default values.
|
||||
*/
|
||||
public function getHeight(): string
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this extension displays the content of the YouTube feed.
|
||||
* You have to call loadConfigValues() before this one, otherwise you get default values.
|
||||
*/
|
||||
public function isShowContent(): bool
|
||||
{
|
||||
return $this->showContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this extension should use youtube-nocookie.com instead of youtube.com.
|
||||
* You have to call loadConfigValues() before this one, otherwise you get default values.
|
||||
*/
|
||||
public function isUseNoCookieDomain(): bool
|
||||
{
|
||||
return $this->useNoCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the YouTube video iframe into the content of an entry, if the entries link points to a YouTube watch URL.
|
||||
* @throws FreshRSS_Context_Exception
|
||||
*/
|
||||
public function embedYouTubeVideo(FreshRSS_Entry $entry): FreshRSS_Entry
|
||||
{
|
||||
$link = $entry->link();
|
||||
|
||||
// if (preg_match('#^https?://www\.youtube\.com/watch\?v=|/videos/watch/[0-9a-f-]{36}$#', $link) !== 1) {
|
||||
// return $entry;
|
||||
// }
|
||||
if (preg_match('#^https?://www\.youtube\.com/watch\?v=|/videos/watch/#', $link) !== 1) {
|
||||
return $entry;
|
||||
}
|
||||
|
||||
$this->loadConfigValues();
|
||||
|
||||
if (stripos($entry->content(), '<iframe class="youtube-plugin-video"') !== false) {
|
||||
return $entry;
|
||||
}
|
||||
if (stripos($link, 'www.youtube.com/watch?v=') !== false) {
|
||||
$html = $this->getHtmlContentForLink($entry, $link);
|
||||
} else { //peertube
|
||||
$html = $this->getHtmlPeerTubeContentForLink($entry, $link);
|
||||
}
|
||||
|
||||
$entry->_content($html);
|
||||
$entry->_attribute('enclosures', []);
|
||||
$entry->_attribute('thumbnail', '');
|
||||
$entry->_attribute('thumbnails', '');
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML <iframe> for a given Youtube watch URL (www.youtube.com/watch?v=)
|
||||
*/
|
||||
public function getHtmlContentForLink(FreshRSS_Entry $entry, string $link): string
|
||||
{
|
||||
$domain = 'www.youtube.com';
|
||||
if ($this->useNoCookie) {
|
||||
$domain = 'www.youtube-nocookie.com';
|
||||
}
|
||||
$url = str_replace('//www.youtube.com/watch?v=', '//' . $domain . '/embed/', $link);
|
||||
$url = str_replace('http://', 'https://', $url);
|
||||
|
||||
return $this->getHtml($entry, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML <iframe> for a given PeerTube watch URL
|
||||
*/
|
||||
public function getHtmlPeerTubeContentForLink(FreshRSS_Entry $entry, string $link): string
|
||||
{
|
||||
$url = str_replace('/watch', '/embed', $link);
|
||||
|
||||
return $this->getHtml($entry, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML <iframe> for a given URL for the configured width and height, with content ignored, appended or formatted.
|
||||
*/
|
||||
public function getHtml(FreshRSS_Entry $entry, string $url): string
|
||||
{
|
||||
$content = '';
|
||||
|
||||
$iframe = '<iframe class="youtube-plugin-video"
|
||||
style="width:' . $this->getWidth() . '"
|
||||
width="640"
|
||||
height="385"
|
||||
src="' . $url . '"
|
||||
frameborder="0"
|
||||
allowFullScreen></iframe>';
|
||||
|
||||
if ($this->showContent === True) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->encoding = 'UTF-8';
|
||||
$doc->recover = true;
|
||||
$doc->strictErrorChecking = false;
|
||||
|
||||
if ($doc->loadHTML('<?xml encoding="utf-8" ?>' . $entry->content())) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
/** @var DOMNodeList<DOMElement> $titles */
|
||||
$titles = $xpath->evaluate("//*[@class='enclosure-title']");
|
||||
/** @var DOMNodeList<DOMElement> $thumbnails */
|
||||
$thumbnails = $xpath->evaluate("//*[@class='enclosure-thumbnail']/@src");
|
||||
/** @var DOMNodeList<DOMElement> $descriptions */
|
||||
$descriptions = $xpath->evaluate("//*[@class='enclosure-description']");
|
||||
|
||||
$content = '<div class="enclosure">';
|
||||
|
||||
// We hide the title so it doesn't appear in the final article, which would be redundant with the RSS article title,
|
||||
// but we keep it in the content anyway, so RSS clients can extract it if needed.
|
||||
if ($titles->length > 0 && $titles[0] instanceof DOMNode) {
|
||||
$content .= '<p class="enclosure-title" hidden>' . $titles[0]->nodeValue . '</p>';
|
||||
}
|
||||
|
||||
// We hide the thumbnail so it doesn't appear in the final article, which would be redundant with the YouTube player preview,
|
||||
// but we keep it in the content anyway, so RSS clients can extract it to display a preview where it wants (in article listing,
|
||||
// by example, like with Reeder).
|
||||
if ($thumbnails->length > 0 && $thumbnails[0] instanceof DOMNode) {
|
||||
$content .= '<p hidden><img class="enclosure-thumbnail" src="' . $thumbnails[0]->nodeValue . '" alt="" /></p>';
|
||||
}
|
||||
|
||||
$content .= $iframe;
|
||||
|
||||
if ($descriptions->length > 0 && $descriptions[0] instanceof DOMNode) {
|
||||
$content .= '<p class="enclosure-description">' . nl2br(htmlspecialchars($descriptions[0]->nodeValue ?? '', ENT_COMPAT, 'UTF-8'), use_xhtml: true) . '</p>';
|
||||
}
|
||||
|
||||
$content .= "</div>\n";
|
||||
} else {
|
||||
$content = $iframe . $entry->content();
|
||||
}
|
||||
} else {
|
||||
$content = $iframe;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by FreshRSS when the configuration page is loaded, and when configuration is saved.
|
||||
* - We save configuration in case of a post.
|
||||
* - We (re)load configuration in all case, so they are in-sync after a save and before a page load.
|
||||
* @throws FreshRSS_Context_Exception
|
||||
*/
|
||||
#[\Override]
|
||||
public function handleConfigureAction(): void
|
||||
{
|
||||
$this->registerTranslates();
|
||||
|
||||
if (Minz_Request::isPost()) {
|
||||
FreshRSS_Context::userConf()->_attribute('ytw_height', Minz_Request::paramString('ytw_height'));
|
||||
FreshRSS_Context::userConf()->_attribute('ytw_width', Minz_Request::paramString('ytw_width'));
|
||||
FreshRSS_Context::userConf()->_attribute('ytw_show_content', Minz_Request::paramBoolean('ytw_show_content'));
|
||||
FreshRSS_Context::userConf()->_attribute('ytw_nocookie', Minz_Request::paramInt('ytw_nocookie'));
|
||||
FreshRSS_Context::userConf()->save();
|
||||
}
|
||||
|
||||
$this->loadConfigValues();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue