Merge branch 'master' into FabBridge

This commit is contained in:
Tobias Alexander Franke 2025-07-10 18:57:35 +01:00 committed by GitHub
commit 71396becd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 204 additions and 416 deletions

View file

@ -1,8 +1,21 @@
FROM rssbridge/rss-bridge:latest FROM rssbridge/rss-bridge:latest
COPY --chmod=755 post-create-command.sh /usr/local/bin/post-create-command
ADD https://raw.githubusercontent.com/docker-library/php/master/docker-php-ext-enable /usr/local/bin/docker-php-ext-enable
RUN chmod u+x /usr/local/bin/docker-php-ext-enable
ADD https://getcomposer.org/installer /usr/local/bin/composer-installer.php
RUN chmod u+x /usr/local/bin/composer-installer.php
RUN php /usr/local/bin/composer-installer.php --check && \
php /usr/local/bin/composer-installer.php --filename=composer --install-dir=/usr/local/bin
RUN apt-get update && \ RUN apt-get update && \
apt-get install --yes --no-install-recommends \ apt-get install -y \
git && \ git \
pecl install xdebug && \ php-dev \
pear install PHP_CodeSniffer && \ make \
docker-php-ext-enable xdebug unzip
RUN pecl install xdebug && \
PHP_INI_DIR=/etc/php/8.2/fpm docker-php-ext-enable xdebug

View file

@ -6,9 +6,9 @@
"vscode": { "vscode": {
// Set *default* container specific settings.json values on container create. // Set *default* container specific settings.json values on container create.
"settings": { "settings": {
"php.validate.executablePath": "/usr/local/bin/php", "php.validate.executablePath": "/usr/bin/php",
"phpSniffer.executablesFolder": "/usr/local/bin/", "phpSniffer.executablesFolder": "/root/.config/composer/vendor/bin",
"phpcs.executablePath": "/usr/local/bin/phpcs", "phpcs.executablePath": "/root/.config/composer/vendor/bin/phpcs",
"phpcs.lintOnType": false "phpcs.lintOnType": false
}, },
@ -22,6 +22,9 @@
] ]
} }
}, },
"remoteEnv": {
"PATH": "${containerEnv:PATH}:/root/.config/composer/vendor/bin",
},
"forwardPorts": [3100, 9000, 9003], "forwardPorts": [3100, 9000, 9003],
"postCreateCommand": "cp .devcontainer/nginx.conf /etc/nginx/conf.d/default.conf && cp .devcontainer/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini && mkdir .vscode && cp .devcontainer/launch.json .vscode && echo '*' > whitelist.txt && chmod a+x \"$(pwd)\" && rm -rf /var/www/html && ln -s \"$(pwd)\" /var/www/html && nginx && php-fpm -D" "postCreateCommand": "/usr/local/bin/post-create-command"
} }

View file

@ -9,7 +9,8 @@
"type": "php", "type": "php",
"request": "launch", "request": "launch",
"port": 9003, "port": 9003,
"auto": true "auto": true,
"log": true
}, },
{ {
"name": "Launch currently open script", "name": "Launch currently open script",

View file

@ -0,0 +1,27 @@
#/bin/sh
cp .devcontainer/nginx.conf /etc/nginx/conf.d/default.conf
cp .devcontainer/xdebug.ini /etc/php/8.2/fpm/conf.d/xdebug.ini
# This should download some dev-dependencies, like phpunit and the PHP code sniffers
composer global require "phpunit/phpunit:^9"
composer global require "squizlabs/php_codesniffer:^3.6"
composer global require "phpcompatibility/php-compatibility:^9.3"
# We need to this manually for running the PHPCompatibility ruleset
phpcs --config-set installed_paths /root/.config/composer/vendor/phpcompatibility/php-compatibility
mkdir -p .vscode
cp .devcontainer/launch.json .vscode
echo '*' > whitelist.txt
chmod a+x $(pwd)
rm -rf /var/www/html
ln -s $(pwd) /var/www/html
# Solves possible issue of cache directory not being accessible
chown www-data:www-data -R $(pwd)/cache
nginx
php-fpm8.2 -D

View file

@ -8,7 +8,7 @@ on:
jobs: jobs:
phpcs: phpcs:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
php-versions: ['7.4'] php-versions: ['7.4']
@ -21,7 +21,7 @@ jobs:
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p - run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
phpcompatibility: phpcompatibility:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
php-versions: ['7.4'] php-versions: ['7.4']
@ -36,7 +36,7 @@ jobs:
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p - run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p
executable_php_files_check: executable_php_files_check:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: | - run: |

View file

@ -8,7 +8,7 @@ on:
jobs: jobs:
phpunit8: phpunit8:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['7.4', '8.0', '8.1']

View file

@ -1,113 +0,0 @@
<?php
class CuriousCatBridge extends BridgeAbstract
{
const NAME = 'Curious Cat Bridge';
const URI = 'https://curiouscat.me';
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [[
'username' => [
'name' => 'Username',
'type' => 'text',
'required' => true,
'exampleValue' => 'koethekoethe',
]
]];
const CACHE_TIMEOUT = 3600;
public function collectData()
{
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
$apiJson = getContents($url);
$apiData = Json::decode($apiJson);
if (isset($apiData['error'])) {
throw new \Exception($apiData['error_code']);
}
foreach ($apiData['posts'] as $post) {
$item = [];
$item['author'] = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$item['author'] = $post['senderData']['username'];
}
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
$item['title'] = $this->ellipsisTitle($post['comment']);
$item['content'] = $this->processContent($post);
$item['timestamp'] = $post['timestamp'];
$this->items[] = $item;
}
}
public function getURI()
{
if (!is_null($this->getInput('username'))) {
return self::URI . '/' . $this->getInput('username');
}
return parent::getURI();
}
public function getName()
{
if (!is_null($this->getInput('username'))) {
return $this->getInput('username') . ' - Curious Cat';
}
return parent::getName();
}
private function processContent($post)
{
$author = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$authorUrl = self::URI . '/' . $post['senderData']['username'];
$author = <<<EOD
<a href="{$authorUrl}">{$post['senderData']['username']}</a>
EOD;
}
$question = $this->formatUrls($post['comment']);
$answer = $this->formatUrls($post['reply']);
$content = <<<EOD
<p>{$author} asked:</p>
<blockquote>{$question}</blockquote><br/>
<p>{$post['addresseeData']['username']} answered:</p>
<blockquote>{$answer}</blockquote>
EOD;
return $content;
}
private function ellipsisTitle($text)
{
$length = 150;
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
);
}
}

View file

@ -51,16 +51,21 @@ class EpicGamesFreeBridge extends BridgeAbstract
$data = $json['data']['Catalog']['searchStore']['elements']; $data = $json['data']['Catalog']['searchStore']['elements'];
foreach ($data as $element) { foreach ($data as $element) {
if (!isset($element['promotions']['promotionalOffers'][0])) { $promo = $element['promotions']['promotionalOffers'][0]['promotionalOffers'][0] ?? false;
if (
!$promo ||
$promo['discountSetting']['discountType'] !== 'PERCENTAGE' ||
$promo['discountSetting']['discountPercentage'] !== 0
) {
continue; continue;
} }
$item = [ $item = [
'author' => $element['seller']['name'], 'author' => $element['seller']['name'],
'content' => $element['description'], 'content' => $element['description'],
'enclosures' => array_map(fn($item) => $item['url'], $element['keyImages']), 'enclosures' => array_map(fn($item) => $item['url'], $element['keyImages']),
'timestamp' => strtotime($element['promotions']['promotionalOffers'][0]['promotionalOffers'][0]['startDate']), 'timestamp' => strtotime($promo['startDate']),
'title' => $element['title'], 'title' => $element['title'],
'url' => parent::getURI() . $this->getInput('locale') . '/p/' . $element['urlSlug'], 'uri' => parent::getURI() . $this->getInput('locale') . '/p/' . $element['productSlug'],
]; ];
$this->items[] = $item; $this->items[] = $item;
} }

View file

@ -10,6 +10,7 @@ class FabBridge extends BridgeAbstract
public function collectData() public function collectData()
{ {
$responseheaders = get_headers(static::URI); $responseheaders = get_headers(static::URI);
$csrf = ""; $csrf = "";
@ -27,8 +28,8 @@ class FabBridge extends BridgeAbstract
'Accept: application/json, text/plain, */*', 'Accept: application/json, text/plain, */*',
'Accept-Language: en', 'Accept-Language: en',
'Accept-Encoding: gzip, deflate, br, zstd', 'Accept-Encoding: gzip, deflate, br, zstd',
'Referer: ' . static::URI,
'Cookie: ' . $csrf, 'Cookie: ' . $csrf,
'Referer: ' . static::URI
]; ];
$json = getContents($url, $header); $json = getContents($url, $header);

View file

@ -31,25 +31,33 @@ class GoComicsBridge extends BridgeAbstract
public function collectData() public function collectData()
{ {
$link = $this->getURI(); $link = $this->getURI();
$landingpage = getSimpleHTMLDOM($link);
$element = $landingpage->find('div[data-post-url]', 0);
if ($element) {
$link = $element->getAttribute('data-post-url');
} else { // fallback for comics without data-post-url (assumes daily comic)
$nextcomiclink = $landingpage->find('a[class*="ComicNavigation_controls__button_previous__"]', 0)->href;
preg_match('/(\d{4}\/\d{2}\/\d{2})/', $nextcomiclink, $nclmatches);
if (!empty($nclmatches[1])) {
$nextdate = new DateTime($nclmatches[1]);
$nextdate = $nextdate->modify('+1 day')->format('Y/m/d');
$link = $link . '/' . $nextdate;
} else {
throw new \Exception('Could not find the first comic URL. Please create a new GitHub issue.');
}
}
for ($i = 0; $i < $this->getInput('limit'); $i++) { for ($i = 0; $i < $this->getInput('limit'); $i++) {
$html = getSimpleHTMLDOM($link); $html = getSimpleHTMLDOM($link);
// get json data from the first page
$json = $html->find('div[class^="ShowComicViewer_showComicViewer__comic__"] script[type="application/ld+json"]', 0)->innertext; $imagelink = $html->find('meta[property="og:image"]', 0)->content;
$data = json_decode($json, false); $parts = explode('/', $link);
$date = DateTime::createFromFormat('Y/m/d', implode('/', array_slice($parts, -3)));
$title = $html->find('meta[property="og:title"]', 0)->content;
preg_match('/by (.*?) for/', $title, $authormatches);
$author = $authormatches[1] ?? 'GoComics';
$item = []; $item = [];
$author = $data->author->name;
$imagelink = $data->contentUrl;
$date = $data->datePublished;
$title = $data->name . ' - GoComics';
// get a permlink for this day's comic if there isn't one specified
if ($link === $this->getURI()) {
$link = $this->getURI() . '/' . DateTime::createFromFormat('F j, Y', $date)->format('Y/m/d');
}
$item['id'] = $imagelink; $item['id'] = $imagelink;
$item['uri'] = $link; $item['uri'] = $link;
$item['author'] = $author; $item['author'] = $author;
@ -57,7 +65,7 @@ class GoComicsBridge extends BridgeAbstract
if ($this->getInput('date-in-title') === true) { if ($this->getInput('date-in-title') === true) {
$item['title'] = $title; $item['title'] = $title;
} }
$item['timestamp'] = DateTime::createFromFormat('F j, Y', $date)->setTime(0, 0, 0)->getTimestamp(); $item['timestamp'] = $date->setTime(0, 0, 0)->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />'; $item['content'] = '<img src="' . $imagelink . '" />';
$link = rtrim(self::URI, '/') . $html->find('a[class*="ComicNavigation_controls__button_previous__"]', 0)->href; $link = rtrim(self::URI, '/') . $html->find('a[class*="ComicNavigation_controls__button_previous__"]', 0)->href;

View file

@ -152,7 +152,7 @@ class GolemBridge extends FeedExpander
$img->src = $img->getAttribute('data-src-full'); $img->src = $img->getAttribute('data-src-full');
} }
foreach ($content->find('p, h1, h2, h3, img[src*="."], iframe, video') as $element) { foreach ($content->find('p, h1, h2, h3, pre, img[src*="."], iframe, video') as $element) {
$item .= $element; $item .= $element;
} }

View file

@ -35,6 +35,16 @@ class IdealoBridge extends BridgeAbstract
] ]
]; ];
private $headers = [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.5,en;q=0.3'
];
private $options = [
CURLOPT_TRANSFER_ENCODING => 1,
CURLOPT_ACCEPT_ENCODING => 'gzip, deflate, br'
];
public function getIcon() public function getIcon()
{ {
return 'https://cdn.idealo.com/storage/ids-assets/ico/favicon.ico'; return 'https://cdn.idealo.com/storage/ids-assets/ico/favicon.ico';
@ -53,10 +63,7 @@ class IdealoBridge extends BridgeAbstract
// The cache does not contain the title of the bridge, we must get it and save it in the cache // The cache does not contain the title of the bridge, we must get it and save it in the cache
if ($product === null) { if ($product === null) {
$header = [ $html = getSimpleHTMLDOM($link, $this->headers, $this->options);
'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15'
];
$html = getSimpleHTMLDOM($link, $header);
$product = $html->find('.oopStage-title', 0)->find('span', 0)->plaintext; $product = $html->find('.oopStage-title', 0)->find('span', 0)->plaintext;
$this->saveCacheValue($keyTITLE, $product); $this->saveCacheValue($keyTITLE, $product);
} }
@ -123,13 +130,8 @@ class IdealoBridge extends BridgeAbstract
} }
public function collectData() public function collectData()
{ {
// Needs header with user-agent to function properly.
$header = [
'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15'
];
$link = $this->getInput('Link'); $link = $this->getInput('Link');
$html = getSimpleHTMLDOM($link, $header); $html = getSimpleHTMLDOM($link, $this->headers, $this->options);
// Get Productname // Get Productname
$titleobj = $html->find('.oopStage-title', 0); $titleobj = $html->find('.oopStage-title', 0);

View file

@ -1,255 +0,0 @@
<?php
class OpenlyBridge extends BridgeAbstract
{
const NAME = 'Openly Bridge';
const URI = 'https://www.openlynews.com/';
const DESCRIPTION = 'Returns news articles';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [
'All News' => [],
'All Opinion' => [],
'By Region' => [
'region' => [
'name' => 'Region',
'type' => 'list',
'values' => [
'Africa' => 'africa',
'Asia Pacific' => 'asia-pacific',
'Europe' => 'europe',
'Latin America' => 'latin-america',
'Middle Easta' => 'middle-east',
'North America' => 'north-america'
]
],
'content' => [
'name' => 'Content',
'type' => 'list',
'values' => [
'News' => 'news',
'Opinion' => 'people'
],
'defaultValue' => 'news'
]
],
'By Tag' => [
'tag' => [
'name' => 'Tag',
'type' => 'text',
'required' => true,
'exampleValue' => 'lgbt-law',
],
'content' => [
'name' => 'Content',
'type' => 'list',
'values' => [
'News' => 'news',
'Opinion' => 'people'
],
'defaultValue' => 'news'
]
],
'By Author' => [
'profileId' => [
'name' => 'Profile ID',
'type' => 'text',
'required' => true,
'exampleValue' => '003D000002WZGYRIA5',
]
]
];
const TEST_DETECT_PARAMETERS = [
'https://www.openlynews.com/profile/?id=0033z00002XUTepAAH' => [
'context' => 'By Author', 'profileId' => '0033z00002XUTepAAH'
],
'https://www.openlynews.com/news/?page=1&theme=lgbt-law' => [
'context' => 'By Tag', 'content' => 'news', 'tag' => 'lgbt-law'
],
'https://www.openlynews.com/news/?page=1&region=north-america' => [
'context' => 'By Region', 'content' => 'news', 'region' => 'north-america'
],
'https://www.openlynews.com/news/?theme=lgbt-law' => [
'context' => 'By Tag', 'content' => 'news', 'tag' => 'lgbt-law'
],
'https://www.openlynews.com/news/?region=north-america' => [
'context' => 'By Region', 'content' => 'news', 'region' => 'north-america'
]
];
const CACHE_TIMEOUT = 900; // 15 mins
const ARTICLE_CACHE_TIMEOUT = 3600; // 1 hour
private $feedTitle = '';
private $itemLimit = 10;
private $profileUrlRegex = '/openlynews\.com\/profile\/\?id=([a-zA-Z0-9]+)/';
private $tagUrlRegex = '/openlynews\.com\/([a-z]+)\/\?(?:page=(?:[0-9]+)&)?theme=([\w-]+)/';
private $regionUrlRegex = '/openlynews\.com\/([a-z]+)\/\?(?:page=(?:[0-9]+)&)?region=([\w-]+)/';
public function detectParameters($url)
{
$params = [];
if (preg_match($this->profileUrlRegex, $url, $matches) > 0) {
$params['context'] = 'By Author';
$params['profileId'] = $matches[1];
return $params;
}
if (preg_match($this->tagUrlRegex, $url, $matches) > 0) {
$params['context'] = 'By Tag';
$params['content'] = $matches[1];
$params['tag'] = $matches[2];
return $params;
}
if (preg_match($this->regionUrlRegex, $url, $matches) > 0) {
$params['context'] = 'By Region';
$params['content'] = $matches[1];
$params['region'] = $matches[2];
return $params;
}
return null;
}
public function collectData()
{
$url = $this->getAjaxURI();
if ($this->queriedContext === 'By Author') {
$url = $this->getURI();
}
$html = getSimpleHTMLDOM($url);
$html = defaultLinkTo($html, $this->getURI());
if ($html->find('h1', 0)) {
$this->feedTitle = $html->find('h1', 0)->plaintext;
}
if ($html->find('h2.title-v4', 0)) {
$html->find('span.tooltiptext', 0)->innertext = '';
$this->feedTitle = $html->find('a.tooltipitem', 0)->plaintext;
}
$items = $html->find('div.item');
$limit = 5;
foreach (array_slice($items, 0, $limit) as $div) {
$this->items[] = $this->getArticle($div->find('a', 0)->href);
if (count($this->items) >= $this->itemLimit) {
break;
}
}
}
public function getURI()
{
switch ($this->queriedContext) {
case 'All News':
return self::URI . 'news';
break;
case 'All Opinion':
return self::URI . 'people';
break;
case 'By Tag':
return self::URI . $this->getInput('content') . '/?theme=' . $this->getInput('tag');
case 'By Region':
return self::URI . $this->getInput('content') . '/?region=' . $this->getInput('region');
break;
case 'By Author':
return self::URI . 'profile/?id=' . $this->getInput('profileId');
break;
default:
return parent::getURI();
}
}
public function getName()
{
switch ($this->queriedContext) {
case 'All News':
return 'News - Openly';
break;
case 'All Opinion':
return 'Opinion - Openly';
break;
case 'By Tag':
if (empty($this->feedTitle)) {
$this->feedTitle = $this->getInput('tag');
}
if ($this->getInput('content') === 'people') {
return $this->feedTitle . ' - Opinion - Openly';
}
return $this->feedTitle . ' - Openly';
break;
case 'By Region':
if (empty($this->feedTitle)) {
$this->feedTitle = $this->getInput('region');
}
if ($this->getInput('content') === 'people') {
return $this->feedTitle . ' - Opinion - Openly';
}
return $this->feedTitle . ' - Openly';
break;
case 'By Author':
if (empty($this->feedTitle)) {
$this->feedTitle = $this->getInput('profileId');
}
return $this->feedTitle . ' - Author - Openly';
break;
default:
return parent::getName();
}
}
private function getAjaxURI()
{
$part = '/ajax.html?';
switch ($this->queriedContext) {
case 'All News':
return self::URI . 'news' . $part;
break;
case 'All Opinion':
return self::URI . 'people' . $part;
break;
case 'By Tag':
return self::URI . $this->getInput('content') . $part . 'theme=' . $this->getInput('tag');
break;
case 'By Region':
return self::URI . $this->getInput('content') . $part . 'region=' . $this->getInput('region');
break;
}
}
private function getArticle($url)
{
$article = getSimpleHTMLDOMCached($url, self::ARTICLE_CACHE_TIMEOUT);
$article = defaultLinkTo($article, $this->getURI());
$item = [];
$item['title'] = $article->find('h1', 0)->plaintext;
$item['uri'] = $url;
$item['content'] = $article->find('div.body-text', 0);
$item['enclosures'][] = $article->find('meta[name="twitter:image"]', 0)->content;
$item['timestamp'] = $article->find('div.meta.small', 0)->plaintext;
if ($article->find('div.meta a', 0)) {
$item['author'] = $article->find('div.meta a', 0)->plaintext;
}
foreach ($article->find('div.themes li') as $li) {
$item['categories'][] = trim(htmlspecialchars($li->plaintext, ENT_QUOTES));
}
return $item;
}
}

View file

@ -0,0 +1,95 @@
<?php
class PaulGrahamBridge extends BridgeAbstract
{
const NAME = 'Paul Graham Essays';
const URI = 'https://www.paulgraham.com/articles.html';
const DESCRIPTION = 'Returns the latest Paul Graham essays in display order';
const MAINTAINER = 'Claire (for Stéphane)';
const CACHE_TIMEOUT = 3600;
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI);
// Navigate to the right TD
// /html/body/table/tbody/tr/td[3]
$tables = $html->find('body table');
if (!isset($tables[0])) {
return;
}
$tds = $tables[0]->find('td');
if (!isset($tds[2])) {
return;
}
$contentTd = $tds[2];
// Find all inner tables (each one holds a single essay link)
$essayTables = $contentTd->find('table');
if (!isset($essayTables[1])) {
return;
}
$essayTable = $essayTables[1];
// /html/body/table/tbody/tr/td[3]/table[2]/tbody/tr[2]/td/font/a
$links = $essayTable->find('font');
$essayLinks = [];
foreach ($links as $t) {
$link = $t->find('a', 0);
if (!$link) {
continue;
}
$href = trim($link->href);
$title = trim($link->plaintext);
if (empty($href) || strpos($href, 'http') === 0 || !preg_match('/\.html$/', $href)) {
continue;
}
$essayLinks[] = [
'title' => $title,
'url' => 'https://www.paulgraham.com/' . $href,
];
}
// Only fetch the first 10 (in display order)
$essayLinks = array_slice($essayLinks, 0, 10);
foreach ($essayLinks as $essay) {
$item = [
'uri' => $essay['url'],
'title' => $essay['title'],
'uid' => $essay['url'],
'content' => '',
];
$essayHtml = getSimpleHTMLDOMCached($essay['url']);
if ($essayHtml) {
$essayTables = $essayHtml->find('body table');
if (isset($essayTables[0])) {
$essayTds = $essayTables[0]->find('td');
if (isset($essayTds[2])) {
$mainContent = $essayTds[2]->innertext;
$mainDom = str_get_html($mainContent);
// Strip unwanted layout elements
foreach ($mainDom->find('map, img, script') as $el) {
$el->outertext = '';
}
$item['content'] = $mainDom->save();
}
}
}
$this->items[] = $item;
}
}
}

View file

@ -104,7 +104,7 @@ class TelegramBridge extends BridgeAbstract
$notSupported = $messageDiv->find('div.message_media_not_supported_wrap', 0); $notSupported = $messageDiv->find('div.message_media_not_supported_wrap', 0);
if ($notSupported) { if ($notSupported) {
// For unknown reasons, the telegram preview page omits the content of this post // For unknown reasons, the telegram preview page omits the content of this post
$message = 'RSS-Bridge was unable to find the content of this post.<br><br>' . $notSupported->innertext; $message = (string) $notSupported->innertext;
} }
if ($messageDiv->find('div.tgme_widget_message_forwarded_from', 0)) { if ($messageDiv->find('div.tgme_widget_message_forwarded_from', 0)) {

View file

@ -92,6 +92,7 @@ class CacheFactory
if (empty($port)) { if (empty($port)) {
throw new \Exception('"port" param is not set for ' . $section); throw new \Exception('"port" param is not set for ' . $section);
} }
$port = (string) $port;
if (!ctype_digit($port)) { if (!ctype_digit($port)) {
throw new \Exception('"port" param is invalid for ' . $section); throw new \Exception('"port" param is invalid for ' . $section);
} }

View file

@ -7,7 +7,7 @@
*/ */
final class Configuration final class Configuration
{ {
private const VERSION = '2025-01-26'; private const VERSION = '2025-06-03';
private static $config = []; private static $config = [];