1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-26 09:34:46 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Eugene Molotov
a34eed1fcc Update README.md 2020-10-14 18:48:30 +05:00
Eugene Molotov
2f615693af Update README.md 2020-10-14 17:59:32 +05:00
95 changed files with 6674 additions and 4202 deletions

View File

@@ -1,35 +0,0 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpcs:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpcs
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
phpcompatibility:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
- run: composer global require phpcompatibility/php-compatibility
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p

View File

@@ -1,47 +0,0 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpunit6:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.0', '7.1', '7.2']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^6
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit7:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.1', '7.2', '7.3']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^7
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit8:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^8
- run: phpunit --configuration=phpunit.xml --include-path=lib/

46
.travis.yml Normal file
View File

@@ -0,0 +1,46 @@
dist: trusty
language: php
install:
- composer global require dealerdirect/phpcodesniffer-composer-installer;
- composer global require phpcompatibility/php-compatibility;
- if [[ "$PHPUNIT" ]]; then
composer global require phpunit/phpunit ^$PHPUNIT;
fi
script:
- phpenv rehash
# Run PHP_CodeSniffer on all versions
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
# Check PHP compatibility for the lowest and highest supported version
- if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
fi
# Run unit tests on highest major version
- if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then
~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/;
fi
php:
- 7.3
env:
- PHPUNIT=6
- PHPUNIT=7
- PHPUNIT=8
matrix:
fast_finish: true
include:
- php: 5.6
env: PHPUNIT=
- php: 7.0
- php: 7.1
- php: 7.2
allow_failures:
- php: 7.3
env: PHPUNIT=7
- php: 7.3
env: PHPUNIT=8

View File

@@ -7,7 +7,6 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& apt-get --yes --no-install-recommends install \
zlib1g-dev \
libmemcached-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \

View File

@@ -1,10 +1,10 @@
![RSS-Bridge](static/logo_600px.png)
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions) [![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
====
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
**Important**: RSS-Bridge is __not__ aa feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
Supported sites/pages (examples)
===
@@ -65,7 +65,6 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php)
- [`filter`](https://secure.php.net/manual/en/book.filter.php)
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
@@ -110,41 +109,34 @@ We are RSS-Bridge community, a group of developers continuing the project initia
Use this script to generate the list automatically (using the GitHub API):
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
* [86423355844265459587182778](https://github.com/86423355844265459587182778)
* [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [akirk](https://github.com/akirk)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
* [alex73](https://github.com/alex73)
* [alexAubin](https://github.com/alexAubin)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [arnd-s](https://github.com/arnd-s)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
* [AxorPL](https://github.com/AxorPL)
* [ayacoo](https://github.com/ayacoo)
* [az5he6ch](https://github.com/az5he6ch)
* [azdkj532](https://github.com/azdkj532)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [Binnette](https://github.com/Binnette)
* [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel)
* [Chouchen](https://github.com/Chouchen)
* [ckiw](https://github.com/ckiw)
* [cn-tools](https://github.com/cn-tools)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [csisoap](https://github.com/csisoap)
* [cyberjacob](https://github.com/cyberjacob)
* [da2x](https://github.com/da2x)
* [Daiyousei](https://github.com/Daiyousei)
* [dawidsowa](https://github.com/dawidsowa)
* [DevonHess](https://github.com/DevonHess)
* [disk0x](https://github.com/disk0x)
* [DJCrashdummy](https://github.com/DJCrashdummy)
* [Djuuu](https://github.com/Djuuu)
@@ -152,47 +144,32 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [dominik-th](https://github.com/dominik-th)
* [Draeli](https://github.com/Draeli)
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
* [drego85](https://github.com/drego85)
* [drklee3](https://github.com/drklee3)
* [em92](https://github.com/em92)
* [eMerzh](https://github.com/eMerzh)
* [EtienneM](https://github.com/EtienneM)
* [fanch317](https://github.com/fanch317)
* [fivefilters](https://github.com/fivefilters)
* [floviolleau](https://github.com/floviolleau)
* [fluffy-critter](https://github.com/fluffy-critter)
* [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
* [ggiessen](https://github.com/ggiessen)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [Glandos](https://github.com/Glandos)
* [gloony](https://github.com/gloony)
* [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [gsantner](https://github.com/gsantner)
* [guigot](https://github.com/guigot)
* [hollowleviathan](https://github.com/hollowleviathan)
* [hpacleb](https://github.com/hpacleb)
* [hunhejj](https://github.com/hunhejj)
* [husim0](https://github.com/husim0)
* [IceWreck](https://github.com/IceWreck)
* [j0k3r](https://github.com/j0k3r)
* [JackNUMBER](https://github.com/JackNUMBER)
* [jacquesh](https://github.com/jacquesh)
* [JasonGhent](https://github.com/JasonGhent)
* [jcgoette](https://github.com/jcgoette)
* [jdesgats](https://github.com/jdesgats)
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [JimDog546](https://github.com/JimDog546)
* [Jocker666z](https://github.com/Jocker666z)
* [johnnygroovy](https://github.com/johnnygroovy)
* [johnpc](https://github.com/johnpc)
* [joni1993](https://github.com/joni1993)
* [joshcoales](https://github.com/joshcoales)
* [killruana](https://github.com/killruana)
* [klimplant](https://github.com/klimplant)
* [kolarcz](https://github.com/kolarcz)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [l1n](https://github.com/l1n)
@@ -201,7 +178,6 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [Leomaradan](https://github.com/Leomaradan)
* [liamka](https://github.com/liamka)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
@@ -212,76 +188,52 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [metaMMA](https://github.com/metaMMA)
* [mibe](https://github.com/mibe)
* [mightymt](https://github.com/mightymt)
* [mitsukarenai](https://github.com/mitsukarenai)
* [Monocularity](https://github.com/Monocularity)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mschwld](https://github.com/mschwld)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [Niehztog](https://github.com/Niehztog)
* [Nono-m0le](https://github.com/Nono-m0le)
* [ObsidianWitch](https://github.com/ObsidianWitch)
* [OliverParoczai](https://github.com/OliverParoczai)
* [Ololbu](https://github.com/Ololbu)
* [oratosquilla-oratoria](https://github.com/oratosquilla-oratoria)
* [ORelio](https://github.com/ORelio)
* [otakuf](https://github.com/otakuf)
* [Park0](https://github.com/Park0)
* [Paroleen](https://github.com/Paroleen)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
* [PeterDaveHello](https://github.com/PeterDaveHello)
* [Peterr-K](https://github.com/Peterr-K)
* [Piranhaplant](https://github.com/Piranhaplant)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [Pofilo](https://github.com/Pofilo)
* [prysme01](https://github.com/prysme01)
* [Qluxzz](https://github.com/Qluxzz)
* [quentinus95](https://github.com/quentinus95)
* [rakoo](https://github.com/rakoo)
* [RawkBob](https://github.com/RawkBob)
* [regisenguehard](https://github.com/regisenguehard)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [ronansalmon](https://github.com/ronansalmon)
* [rremizov](https://github.com/rremizov)
* [sebsauvage](https://github.com/sebsauvage)
* [shutosg](https://github.com/shutosg)
* [simon816](https://github.com/simon816)
* [Simounet](https://github.com/Simounet)
* [somini](https://github.com/somini)
* [squeek502](https://github.com/squeek502)
* [stjohnjohnson](https://github.com/stjohnjohnson)
* [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sunchaserinfo](https://github.com/sunchaserinfo)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [sysadminstory](https://github.com/sysadminstory)
* [t0stiman](https://github.com/t0stiman)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [tgkenney](https://github.com/tgkenney)
* [thefranke](https://github.com/thefranke)
* [ThePadawan](https://github.com/ThePadawan)
* [TheRadialActive](https://github.com/TheRadialActive)
* [theScrabi](https://github.com/theScrabi)
* [thezeroalpha](https://github.com/thezeroalpha)
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
* [triatic](https://github.com/triatic)
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [xurxof](https://github.com/xurxof)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [ZeNairolf](https://github.com/ZeNairolf)
Licenses
@@ -313,6 +265,6 @@ You're not social when you hamper sharing by removing feeds. You're happy to hav
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We are rebuilding bridges you have willfully destroyed.
We are rebuilding bridges you have wilfully destroyed.
Get your shit together: Put RSS/Atom back in.

View File

@@ -131,7 +131,6 @@ class DisplayAction extends ActionAbstract {
try {
$bridge->setDatas($bridge_params);
$bridge->loadConfiguration();
$bridge->collectData();
$items = $bridge->getItems();

View File

@@ -10,7 +10,13 @@ class AlbionOnlineBridge extends BridgeAbstract {
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'type' => 'list',
'values' => array(
'2' => 2,
'5' => 5,
'10' => 10,
'15' => 15,
),
'title' => 'Maximum number of items to return',
'defaultValue' => 5,
),

View File

@@ -32,7 +32,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'Sweden' => 'se',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),

View File

@@ -20,8 +20,6 @@ class AppleMusicBridge extends BridgeAbstract {
));
const CACHE_TIMEOUT = 21600; // 6 hours
private $title;
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
@@ -29,8 +27,6 @@ class AppleMusicBridge extends BridgeAbstract {
$imgSize = $this->getInput('imgSize');
$this->title = $html->find('title', 0)->innertext;
// Grab the json data from the page
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
$html = strstr($html, '{');
@@ -63,8 +59,4 @@ class AppleMusicBridge extends BridgeAbstract {
return $a['timestamp'] < $b['timestamp'];
});
}
public function getName() {
return $this->title ?: parent::getName();
}
}

View File

@@ -1,7 +1,7 @@
<?php
class Arte7Bridge extends BridgeAbstract {
// const MAINTAINER = 'mitsukarenai';
const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min

View File

@@ -125,11 +125,9 @@ class BandcampBridge extends BridgeAbstract {
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$titleElement = $html->find('head meta[name=title]', 0)
or returnServerError('Unable to find title on: ' . $this->getURI());
$this->feedName = $titleElement->content;
$regex = '/band_id=(\d+)/';
if(preg_match($regex, $html, $matches) == false)

View File

@@ -19,11 +19,13 @@ class BastaBridge extends BridgeAbstract {
$item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$item['content'] = $html->find('div.texte', 0)->innertext;
// Replaces all relative image URLs by absolute URLs.
// Relative URLs always start with 'local/'!
$item['content'] = preg_replace(
'/src=["\']{1}([^"\']+)/ims',
'src=\'' . self::URI . '$1\'',
getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext
);
$this->items[] = $item;
$limit++;
}

View File

@@ -1,29 +0,0 @@
<?php
class BleepingComputerBridge extends FeedExpander {
const MAINTAINER = 'csisoap';
const NAME = 'Bleeping Computer';
const URI = 'https://www.bleepingcomputer.com/';
const DESCRIPTION = 'Returns the newest articles.';
protected function parseItem($item){
$item = parent::parseItem($item);
$article_html = getSimpleHTMLDOMCached($item['uri']);
if(!$article_html) {
$item['content'] .= '<p><em>Could not request ' . $this->getName() . ': ' . $item['uri'] . '</em></p>';
return $item;
}
$article_content = $article_html->find('div.articleBody', 0)->innertext;
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
$item['content'] = trim($article_content);
return $item;
}
public function collectData(){
$feed = static::URI . 'feed/';
$this->collectExpandableDatas($feed);
}
}

View File

@@ -1,60 +0,0 @@
<?php
class BlizzardNewsBridge extends XPathAbstract {
const NAME = 'Blizzard News';
const URI = 'https://news.blizzard.com';
const DESCRIPTION = 'Blizzard (game company) newsfeed';
const MAINTAINER = 'Niehztog';
const PARAMETERS = array(
'' => array(
'locale' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'Deutsch' => 'de-de',
'English (EU)' => 'en-gb',
'English (US)' => 'en-us',
'Español (EU)' => 'es-es',
'Español (AL)' => 'es-mx',
'Français' => 'fr-fr',
'Italiano' => 'it-it',
'日本語' => 'ja-jp',
'한국어' => 'ko-kr',
'Polski' => 'pl-pl',
'Português (AL)' => 'pt-br',
'Русский' => 'ru-ru',
'ภาษาไทย' => 'th-th',
'简体中文' => 'zh-cn',
'繁體中文' => 'zh-tw'
),
'defaultValue' => 'en-us',
'title' => 'Select your language'
)
)
);
const CACHE_TIMEOUT = 3600;
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
const SETTING_FIX_ENCODING = true;
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl(){
$locale = $this->getInput('locale');
if('zh-cn' === $locale) {
return 'https://cn.news.blizzard.com';
}
return 'https://news.blizzard.com/' . $locale;
}
}

View File

@@ -16,7 +16,6 @@ class BrutBridge extends BridgeAbstract {
'Entertainment' => 'entertainment',
'Sports' => 'sport',
'Nature' => 'nature',
'Health' => 'health',
),
'defaultValue' => 'news',
),
@@ -27,7 +26,6 @@ class BrutBridge extends BridgeAbstract {
'United States' => 'us',
'United Kingdom' => 'uk',
'France' => 'fr',
'Spain' => 'es',
'India' => 'in',
'Mexico' => 'mx',
),

View File

@@ -1,219 +0,0 @@
<?php
class BukowskisBridge extends BridgeAbstract
{
const NAME = 'Bukowskis';
const URI = 'https://www.bukowskis.com';
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
const MAINTAINER = 'Qluxzz';
const PARAMETERS = array(array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '',
'Art' => array(
'All' => 'art',
'Classic Art' => 'art.classic-art',
'Classic Finnish Art' => 'art.classic-finnish-art',
'Classic Swedish Art' => 'art.classic-swedish-art',
'Contemporary' => 'art.contemporary',
'Modern Finnish Art' => 'art.modern-finnish-art',
'Modern International Art' => 'art.modern-international-art',
'Modern Swedish Art' => 'art.modern-swedish-art',
'Old Masters' => 'art.old-masters',
'Other' => 'art.other',
'Photographs' => 'art.photographs',
'Prints' => 'art.prints',
'Sculpture' => 'art.sculpture',
'Swedish Old Masters' => 'art.swedish-old-masters',
),
'Asian Ceramics & Works of Art' => array(
'All' => 'asian-ceramics-works-of-art',
'Other' => 'asian-ceramics-works-of-art.other',
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
),
'Books & Manuscripts' => array(
'All' => 'books-manuscripts',
'Books' => 'books-manuscripts.books',
),
'Carpets, rugs & textiles' => array(
'All' => 'carpets-rugs-textiles',
'European' => 'carpets-rugs-textiles.european',
'Oriental' => 'carpets-rugs-textiles.oriental',
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
),
'Ceramics & porcelain' => array(
'All' => 'ceramics-porcelain',
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
'European' => 'ceramics-porcelain.european',
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
'Scandinavian' => 'ceramics-porcelain.scandinavian',
),
'Collectibles' => array(
'All' => 'collectibles',
'Advertising & Retail' => 'collectibles.advertising-retail',
'Memorabilia' => 'collectibles.memorabilia',
'Movies & music' => 'collectibles.movies-music',
'Other' => 'collectibles.other',
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
'Technica & Nautica' => 'collectibles.technica-nautica',
'Toys' => 'collectibles.toys',
),
'Design' => array(
'All' => 'design',
'Art glass' => 'design.art-glass',
'Furniture' => 'design.furniture',
'Other' => 'design.other',
),
'Folk art' => array(
'All' => 'folk-art',
'All categories' => 'lots',
),
'Furniture' => array(
'All' => 'furniture',
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
'Chairs' => 'furniture.chairs',
'Garden furniture' => 'furniture.garden-furniture',
'Mirrors' => 'furniture.mirrors',
'Other' => 'furniture.other',
'Shelves & Book cases' => 'furniture.shelves-book-cases',
'Tables' => 'furniture.tables',
),
'Glassware' => array(
'All' => 'glassware',
'Glassware' => 'glassware.glassware',
'Other' => 'glassware.other',
),
'Jewellery' => array(
'All' => 'jewellery',
'Bracelets' => 'jewellery.bracelets',
'Brooches' => 'jewellery.brooches',
'Earrings' => 'jewellery.earrings',
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
'Other' => 'jewellery.other',
'Rings' => 'jewellery.rings',
),
'Lighting' => array(
'All' => 'lighting',
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
'Ceiling lights' => 'lighting.ceiling-lights',
'Chandeliers' => 'lighting.chandeliers',
'Floor lights' => 'lighting.floor-lights',
'Other' => 'lighting.other',
'Table lights' => 'lighting.table-lights',
'Wall lights' => 'lighting.wall-lights',
),
'Militaria' => array(
'All' => 'militaria',
'Honors & Medals' => 'militaria.honors-medals',
'Other militaria' => 'militaria.other-militaria',
'Weaponry' => 'militaria.weaponry',
),
'Miscellaneous' => array(
'All' => 'miscellaneous',
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
'Nickel silver' => 'miscellaneous.nickel-silver',
'Oriental' => 'miscellaneous.oriental',
'Other' => 'miscellaneous.other',
),
'Silver' => array(
'All' => 'silver',
'Candle sticks' => 'silver.candle-sticks',
'Cups & Bowls' => 'silver.cups-bowls',
'Cutlery' => 'silver.cutlery',
'Other' => 'silver.other',
),
'Timepieces' => array(
'All' => 'timepieces',
'Other' => 'timepieces.other',
'Pocket watches' => 'timepieces.pocket-watches',
'Table clocks' => 'timepieces.table-clocks',
'Wrist watches' => 'timepieces.wrist-watches',
),
'Vintage & Fashion' => array(
'All' => 'vintage-fashion',
'Accessories' => 'vintage-fashion.accessories',
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
'Clothes' => 'vintage-fashion.clothes',
),
)
),
'sort_order' => array(
'name' => 'Sort order',
'type' => 'list',
'values' => array(
'Ending soon' => 'ending',
'Most recent' => 'recent',
'Most bids' => 'most',
'Fewest bids' => 'fewest',
'Lowest price' => 'lowest',
'Highest price' => 'highest',
'Lowest estimate' => 'low',
'Highest estimate' => 'high',
'Alphabetical' => 'alphabetical',
),
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'Swedish' => 'sv',
'Finnish' => 'fi'
),
),
));
const CACHE_TIMEOUT = 3600; // 1 hour
private $title;
public function collectData()
{
$baseUrl = 'https://www.bukowskis.com';
$category = $this->getInput('category');
$language = $this->getInput('language');
$sort_order = $this->getInput('sort_order');
$url = $baseUrl . '/' . $language . '/lots';
if ($category)
$url = $url . '/category/' . $category;
if ($sort_order)
$url = $url . '/sort/' . $sort_order;
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
foreach ($html->find('div.c-lot-index-lot') as $lot) {
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
$images = json_decode(
htmlspecialchars_decode(
$lot
->find('img.o-aspect-ratio__image', 0)
->getAttribute('data-thumbnails')
)
);
$this->items[] = array(
'title' => $title,
'uri' => $baseUrl . $relative_url,
'uid' => $lot->getAttribute('data-lot-id'),
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1),
);
}
}
public function getName()
{
return $this->title ?: parent::getName();
}
}

4
bridges/CeskaTelevizeBridge.php Normal file → Executable file
View File

@@ -74,8 +74,8 @@ class CeskaTelevizeBridge extends BridgeAbstract {
}
}
public function getURI() {
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
public function getUri() {
return isset($this->feedUri) ? $this->feedUri : parent::getUri();
}
public function getName() {

View File

@@ -0,0 +1,28 @@
<?php
class ChristianDailyReporterBridge extends BridgeAbstract {
const MAINTAINER = 'rogerdc';
const NAME = 'Christian Daily Reporter Unofficial RSS';
const URI = 'https://www.christiandailyreporter.com/';
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
// const CACHE_TIMEOUT = 86400; // 1 day
public function getIcon() {
return self::URI . 'images/cdrfavicon.png';
}
public function collectData() {
$uri = 'https://www.christiandailyreporter.com/';
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Christian Daily Reporter.');
foreach($html->find('div.top p a,div.column p a') as $element) {
$item = array();
// Title
$item['title'] = $element->innertext;
// URL
$item['uri'] = $element->href;
$this->items[] = $item;
}
}
}

View File

@@ -42,7 +42,6 @@ class DiarioDeNoticiasBridge extends BridgeAbstract {
}
return $name;
}
public function getURI() {
switch($this->queriedContext) {
case 'Tag':

View File

@@ -1,166 +0,0 @@
<?php
class DockerHubBridge extends BridgeAbstract {
const NAME = 'Docker Hub Bridge';
const URI = 'https://hub.docker.com';
const DESCRIPTION = 'Returns new images for a container';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'User Submitted Image' => array(
'user' => array(
'name' => 'User',
'type' => 'text',
'required' => true,
'exampleValue' => 'rssbridge',
),
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'rss-bridge',
)
),
'Official Image' => array(
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'postgres',
)
),
);
const CACHE_TIMEOUT = 3600; // 1 hour
private $apiURL = 'https://hub.docker.com/v2/repositories/';
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
public function detectParameters($url) {
$params = array();
// user submitted image
if(preg_match($this->imageUrlRegex, $url, $matches)) {
$params['context'] = 'User Submitted Image';
$params['user'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// official image
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
$params['context'] = 'Official Image';
$params['repo'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
$json = getContents($this->getApiUrl())
or returnServerError('Could not request: ' . $this->getURI());
$data = json_decode($json, false);
foreach ($data->results as $result) {
$item = array();
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
$item['title'] = $result->name;
$item['uid'] = $result->id;
$item['uri'] = $this->getTagUrl($result->name);
$item['author'] = $result->last_updater_username;
$item['timestamp'] = $result->tag_last_pushed;
$item['content'] = <<<EOD
<Strong>Tag</strong><br>
<p>{$result->name}</p>
<Strong>Last pushed</strong><br>
<p>{$lastPushed}</p>
<Strong>Images</strong><br>
{$this->getImages($result)}
EOD;
$this->items[] = $item;
}
}
public function getURI() {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo();
}
if ($this->getInput('repo')) {
return self::URI . '/r/' . $this->getRepo();
}
return parent::getURI();
}
public function getName() {
if ($this->getInput('repo')) {
return $this->getRepo() . ' - Docker Hub';
}
return parent::getName();
}
private function getRepo() {
if ($this->queriedContext === 'Official Image') {
return $this->getInput('repo');
}
return $this->getInput('user') . '/' . $this->getInput('repo');
}
private function getApiUrl() {
if ($this->queriedContext === 'Official Image') {
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
}
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
}
private function getLayerUrl($name, $digest) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
$this->getRepo() . '/' . $name . '/images/' . $digest;
}
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
}
private function getTagUrl($name) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
}
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
}
private function getImages($result) {
$html = <<<EOD
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
EOD;
foreach ($result->images as $image) {
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
$id = $this->getShortDigestId($image->digest);
$html .= <<<EOD
<tr>
<td><a href="{$layersUrl}">{$id}</a></td>
<td>{$image->os}/{$image->architecture}</td>
</tr>
EOD;
}
return $html . '</tbody></table>';
}
private function getShortDigestId($digest) {
$parts = explode(':', $digest);
return substr($parts[1], 0, 12);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -86,7 +86,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
private function getImageTag($preview_path, $title){
return sprintf(
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>',
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
$this->getFullSizeImagePath($preview_path),
$preview_path,
$title
@@ -94,11 +94,6 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
}
private function getFullSizeImagePath($preview_path){
// Get last image from srcset
$src_set_urls = explode(',', $preview_path);
$url = end($src_set_urls);
$url = explode(' ', $url)[1];
return htmlspecialchars_decode($url);
return explode("?compress=1", $preview_path)[0];
}
}

View File

@@ -14,28 +14,17 @@ class EconomistBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM(self::URI . '/latest/')
or returnServerError('Could not fetch latest updates form The Economist.');
foreach($html->find('div.teaser') as $element) {
$a = $element->find('a.headline-link', 0);
$href = $a->href;
if (substr($href, 0, 4) != 'http')
$href = self::URI . $a->href;
foreach($html->find('article') as $element) {
$a = $element->find('a', 0);
$href = self::URI . $a->href;
$full = getSimpleHTMLDOMCached($href);
$article = $full->find('article', 0);
$header = $article->find('span[itemprop="headline"]', 0);
$headerimg = $article->find('div[itemprop="image"]', 0)->find('img', 0);
$author = $article->find('p[itemprop="byline"]', 0);
$time = $article->find('time', 0);
$content = $article->find('div[itemprop="text"]', 0);
$section = array( $article->find('strong[itemprop="articleSection"]', 0)->plaintext );
// Author
if ($author)
$author = substr($author->innertext, 3, strlen($author));
else
$author = 'The Economist';
$header = $article->find('h1', 0);
$author = $article->find('span[itemprop="author"]', 0);
$time = $article->find('time[itemprop="dateCreated"]', 0);
$content = $article->find('div[itemprop="description"]', 0);
// Remove newsletter subscription box
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
@@ -51,15 +40,19 @@ class EconomistBridge extends BridgeAbstract {
if ($nextprev)
$nextprev->outertext = '';
$section = array( $article->find('h3[itemprop="articleSection"]', 0)->plaintext );
$item = array();
$item['title'] = $header->innertext;
$item['title'] = $header->find('span', 0)->innertext . ': '
. $header->find('span', 1)->innertext;
$item['uri'] = $href;
$item['timestamp'] = strtotime($time->datetime);
$item['author'] = $author;
$item['author'] = $author->innertext;
$item['categories'] = $section;
$item['content'] = '<img style="max-width: 100%" src="'
. $headerimg->src . '">' . $content->innertext;
. $a->find('img', 0)->src . '">' . $content->innertext;
$this->items[] = $item;

View File

@@ -10,7 +10,14 @@ class EpicgamesBridge extends BridgeAbstract {
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'type' => 'list',
'values' => array(
'5' => 5,
'10' => 10,
'15' => 15,
'20' => 20,
'25' => 25,
),
'title' => 'Maximum number of items to return',
'defaultValue' => 10,
),

View File

@@ -1,7 +1,7 @@
<?php
class ExtremeDownloadBridge extends BridgeAbstract {
const NAME = 'Extreme Download';
const URI = 'https://www.extreme-down.tv/';
const URI = 'https://www.extreme-down.ninja/';
const DESCRIPTION = 'Suivi de série sur Extreme Download';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
@@ -81,16 +81,6 @@ class ExtremeDownloadBridge extends BridgeAbstract {
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkType($element)
{
$return = '';

View File

@@ -28,9 +28,9 @@ class FM4Bridge extends BridgeAbstract
)
);
private function getPageData($tag, $page) {
public function getPageData($tag, $page) {
if($tag)
$uri = self::URI . '/tags/' . $tag;
$uri = self::URI . "/tags/" . $tag;
else
$uri = self::URI;
@@ -47,7 +47,7 @@ class FM4Bridge extends BridgeAbstract
$item['uri'] = $article->find('a', 0)->href;
$item['title'] = $article->find('h2', 0)->plaintext;
$item['author'] = $article->find('p[class*=keyword]', 0)->plaintext;
$item['timestamp'] = strtotime($article->find('p[class*=time]', 0)->plaintext);
$item["timestamp"] = strtotime($article->find('p[class*=time]', 0)->plaintext);
if ($this->getInput('loadcontent')) {
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
@@ -58,10 +58,9 @@ class FM4Bridge extends BridgeAbstract
}
return $page_items;
}
public function collectData() {
for ($cur_page = 1; $cur_page <= $this->getInput('pages'); $cur_page++) {
$this->items = array_merge($this->items, $this->getPageData($this->getInput('tag'), $cur_page));
$this->items = array_merge($this->items,$this->getPageData($this->getInput('tag'), $cur_page));
}
}
}

View File

@@ -1,115 +0,0 @@
<?php
class FSecureBlogBridge extends BridgeAbstract {
const NAME = 'F-Secure Blog';
const URI = 'https://blog.f-secure.com';
const DESCRIPTION = 'F-Secure Blog';
const MAINTAINER = 'simon816';
const PARAMETERS = array(
'' => array(
'categories' => array(
'name' => 'Blog categories',
'exampleValue' => 'home-security',
),
'language' => array(
'name' => 'Language',
'defaultValue' => 'en',
),
'oldest_date' => array(
'name' => 'Oldest article date',
'exampleValue' => '-2 months',
),
)
);
public function getURI() {
$lang = $this->getInput('language') or 'en';
if ($lang === 'en') {
return self::URI;
}
return self::URI . "/$lang";
}
public function collectData() {
$this->items = array();
$this->seen = array();
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
$categories = $this->getInput('categories');
if (!empty($categories)) {
foreach (explode(',', $categories) as $cat) {
if (!empty($cat)) {
$this->collectCategory($cat);
}
}
return;
}
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
$url = parse_url($link->href);
if (($pos = strpos($url['path'], '/category/')) !== false) {
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
$this->collectCategory($cat);
}
}
}
private function collectCategory($category) {
$url = $this->getURI() . "/category/$category/";
while ($url) {
$url = $this->collectListing($url);
}
}
// n.b. this relies on articles to be ordered by date so the cutoff works
private function collectListing($url) {
$html = getSimpleHTMLDOMCached($url, 60 * 60);
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
foreach ($items as $item) {
$url = $item->getAttribute('data-url');
if (!$this->collectArticle($url)) {
return null; // Too old, stop collecting
}
}
// Point's to 404 for non-english blog
// $next = $html->find('link[rel=next]', 0);
$next = $html->find('ul.page-numbers a.next', 0);
return $next ? $next->href : null;
}
// Returns a boolean whether to continue collecting articles
// i.e. date is after oldest cutoff
private function collectArticle($url) {
if (array_key_exists($url, $this->seen)) {
return true;
}
$html = getSimpleHTMLDOMCached($url);
$rssItem = array( 'uri' => $url, 'uid' => $url );
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
$dt = $html->find('meta[property=article:published_time]', 0)->content;
// Exit if too old
if (strtotime($dt) < $this->oldest) {
return false;
}
$rssItem['timestamp'] = $dt;
$img = $html->find('meta[property=og:image]', 0);
$rssItem['enclosures'] = $img ? array($img->content) : array();
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
$rssItem['categories'] = array_map(function ($link) {
return trim($link->plaintext);
}, $html->find('.b-single-header__categories .c-category-list a'));
$rssItem['content'] = trim($html->find('article', 0)->innertext);
$this->items[] = $rssItem;
$this->seen[$url] = 1;
return true;
}
}

View File

@@ -175,13 +175,7 @@ class FacebookBridge extends BridgeAbstract {
$header = array();
}
$touchURI = str_replace(
'https://www.facebook',
'https://touch.facebook',
$this->getURI()
);
$html = getSimpleHTMLDOM($touchURI, $header)
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('Failed loading facebook page: ' . $this->getURI());
if(!$this->isPublicGroup($html)) {
@@ -192,18 +186,19 @@ class FacebookBridge extends BridgeAbstract {
$this->groupName = $this->extractGroupName($html);
$posts = $html->find('div.story_body_container')
$posts = $html->find('div.userContentWrapper')
or returnServerError('Failed finding posts!');
foreach($posts as $post) {
$item = array();
$item['uri'] = $this->extractGroupPostURI($post);
$item['title'] = $this->extractGroupPostTitle($post);
$item['author'] = $this->extractGroupPostAuthor($post);
$item['content'] = $this->extractGroupPostContent($post);
$item['enclosures'] = $this->extractGroupPostEnclosures($post);
$item['uri'] = $this->extractGroupURI($post);
$item['title'] = $this->extractGroupTitle($post);
$item['author'] = $this->extractGroupAuthor($post);
$item['content'] = $this->extractGroupContent($post);
$item['timestamp'] = $this->extractGroupTimestamp($post);
$item['enclosures'] = $this->extractGroupEnclosures($post);
$this->items[] = $item;
@@ -220,7 +215,16 @@ class FacebookBridge extends BridgeAbstract {
$urlparts = parse_url($group);
$this->validateHost($urlparts['host']);
if($urlparts['host'] !== parse_url(self::URI)['host']
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
return explode('/', $urlparts['path'])[2];
@@ -232,47 +236,24 @@ class FacebookBridge extends BridgeAbstract {
}
private function validateHost($provided_host) {
// Handle mobile links
if (strpos($provided_host, 'm.') === 0) {
$provided_host = substr($provided_host, strlen('m.'));
}
if (strpos($provided_host, 'touch.') === 0) {
$provided_host = substr($provided_host, strlen('touch.'));
}
$facebook_host = parse_url(self::URI)['host'];
if ($provided_host !== $facebook_host
&& 'www.' . $provided_host !== $facebook_host) {
returnClientError('The host you provided is invalid! Received "'
. $provided_host
. '", expected "'
. $facebook_host
. '"!');
}
}
/**
* @param $html simple_html_dom
* @return bool
*/
private function isPublicGroup($html) {
// Facebook touch just presents a login page for non-public groups
$title = $html->find('title', 0);
return $title->plaintext !== 'Log in to Facebook | Facebook';
// Facebook redirects to the groups about page for non-public groups
$about = $html->find('#pagelet_group_about', 0);
return !($about);
}
private function extractGroupName($html) {
$ogtitle = $html->find('._de1', 0)
$ogtitle = $html->find('meta[property="og:title"]', 0)
or returnServerError('Unable to find group title!');
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES);
return html_entity_decode($ogtitle->content, ENT_QUOTES);
}
private function extractGroupPostURI($post) {
private function extractGroupURI($post) {
$elements = $post->find('a')
or returnServerError('Unable to find URI!');
@@ -281,8 +262,7 @@ class FacebookBridge extends BridgeAbstract {
// Find the one that is a permalink
if(strpos($anchor->href, 'permalink') !== false) {
$arr = explode('?', $anchor->href, 2);
return $arr[0];
return $anchor->href;
}
}
@@ -291,61 +271,57 @@ class FacebookBridge extends BridgeAbstract {
}
private function extractGroupPostContent($post) {
private function extractGroupContent($post) {
$content = $post->find('div._5rgt', 0)
$content = $post->find('div.userContent', 0)
or returnServerError('Unable to find user content!');
$context_text = $content->innertext;
if ($content->next_sibling() !== null) {
$context_text .= $content->next_sibling()->innertext;
}
return $context_text;
return $content->innertext . $content->next_sibling()->innertext;
}
private function extractGroupPostAuthor($post) {
private function extractGroupTimestamp($post) {
$element = $post->find('h3 a', 0)
$element = $post->find('abbr[data-utime]', 0)
or returnServerError('Unable to find timestamp!');
return $element->getAttribute('data-utime');
}
private function extractGroupAuthor($post) {
$element = $post->find('img', 0)
or returnServerError('Unable to find author information!');
return $element->plaintext;
return $element->{'aria-label'};
}
private function extractGroupPostEnclosures($post) {
private function extractGroupEnclosures($post) {
$elements = $post->find('span._6qdm');
if ($post->find('div._5rgt', 0)->next_sibling() !== null) {
array_push($elements, ...$post->find('div._5rgt', 0)->next_sibling()->find('i.img'));
}
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
$enclosures = array();
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
foreach($elements as $enclosure) {
if(preg_match($background_img_regex, $enclosure, $matches) > 0) {
$bg_img_value = trim(html_entity_decode($matches[1], ENT_QUOTES), "'\"");
$bg_img_url = urldecode(preg_replace('/\\\([0-9a-z]{2}) /', '%$1', $bg_img_value));
$enclosures[] = urldecode($bg_img_url);
}
$enclosures[] = $enclosure->src;
}
return empty($enclosures) ? null : $enclosures;
}
private function extractGroupPostTitle($post) {
private function extractGroupTitle($post) {
$element = $post->find('h3', 0)
$element = $post->find('h5', 0)
or returnServerError('Unable to find title!');
if(strpos($element->plaintext, 'shared') === false) {
$content = strip_tags($this->extractGroupPostContent($post));
$content = strip_tags($this->extractGroupContent($post));
return $this->extractGroupPostAuthor($post)
return $this->extractGroupAuthor($post)
. ' posted: '
. substr(
$content,
@@ -372,7 +348,13 @@ class FacebookBridge extends BridgeAbstract {
$urlparts = parse_url($user);
$this->validateHost($urlparts['host']);
if($urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') {
@@ -573,7 +555,7 @@ EOD;
}
// No captcha? We can carry on retrieving page contents :)
// First, we check whether the page is public or not
// First, we check wether the page is public or not
$loginForm = $html->find('._585r', 0);
if($loginForm != null) {

View File

@@ -82,7 +82,7 @@ class FicbookBridge extends BridgeAbstract {
$html = defaultLinkTo($html, self::URI);
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments') {
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments' ) {
$this->titleName = $html->find('.fanfic-main-info > h1', 0)->innertext;
}

View File

@@ -1,104 +0,0 @@
<?php
class FirefoxAddonsBridge extends BridgeAbstract {
const NAME = 'Firefox Add-ons Bridge';
const URI = 'https://addons.mozilla.org/';
const DESCRIPTION = 'Returns version history for a Firefox Add-on.';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'id' => array(
'name' => 'Add-on ID',
'type' => 'text',
'required' => true,
'exampleValue' => 'save-to-the-wayback-machine',
)
)
);
const CACHE_TIMEOUT = 3600;
private $feedName = '';
private $releaseDateRegex = '/Released ([\w, ]+) - ([\w. ]+)/';
private $xpiFileRegex = '/([A-Za-z0-9_.-]+)\.xpi$/';
private $outgoingRegex = '/https:\/\/outgoing.prod.mozaws.net\/v1\/(?:[A-z0-9]+)\//';
private $urlRegex = '/addons\.mozilla\.org\/(?:[\w-]+\/)?firefox\/addon\/([\w-]+)/';
public function detectParameters($url) {
$params = array();
if(preg_match($this->urlRegex, $url, $matches)) {
$params['id'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$this->feedName = $html->find('h1[class="AddonTitle"] > a', 0)->innertext;
$author = $html->find('span.AddonTitle-author > a', 0)->plaintext;
foreach ($html->find('div.AddonVersionCard-content') as $div) {
$item = array();
$item['title'] = $div->find('h2.AddonVersionCard-version', 0)->plaintext;
$item['uid'] = $item['title'];
$item['uri'] = $this->getURI();
$item['author'] = $author;
if (preg_match($this->releaseDateRegex, $div->find('div.AddonVersionCard-fileInfo', 0)->plaintext, $match)) {
$item['timestamp'] = $match[1];
$size = $match[2];
}
$compatibility = $div->find('div.AddonVersionCard-compatibility', 0)->plaintext;
$license = $div->find('p.AddonVersionCard-license', 0)->innertext;
$downloadlink = $div->find('a.InstallButtonWrapper-download-link', 0)->href;
$releaseNotes = $this->removeOutgoinglink($div->find('div.AddonVersionCard-releaseNotes', 0));
if (preg_match($this->xpiFileRegex, $downloadlink, $match)) {
$xpiFilename = $match[0];
}
$item['content'] = <<<EOD
<strong>Release Notes</strong>
<p>{$releaseNotes}</p>
<strong>Compatibility</strong>
<p>{$compatibility}</p>
<strong>License</strong>
<p>{$license}</p>
<strong>Download</strong>
<p><a href="{$downloadlink}">{$xpiFilename}</a> ($size)</p>
EOD;
$this->items[] = $item;
}
}
public function getURI() {
if (!is_null($this->getInput('id'))) {
return self::URI . 'en-US/firefox/addon/' . $this->getInput('id') . '/versions/';
}
return parent::getURI();
}
public function getName() {
if (!empty($this->feedName)) {
return $this->feedName . ' - Firefox Add-on';
}
return parent::getName();
}
private function removeOutgoinglink($html) {
foreach ($html->find('a') as $a) {
$a->href = urldecode(preg_replace($this->outgoingRegex, '', $a->href));
}
return $html->innertext;
}
}

View File

@@ -20,27 +20,6 @@ class FlickrBridge extends BridgeAbstract {
'required' => true,
'title' => 'Insert keyword',
'exampleValue' => 'bird'
),
'media' => array(
'name' => 'Media',
'type' => 'list',
'values' => array(
'All (Photos & videos)' => 'all',
'Photos' => 'photos',
'Videos' => 'videos',
),
'defaultValue' => 'all',
),
'sort' => array(
'name' => 'Sort By',
'type' => 'list',
'values' => array(
'Relevance' => 'relevance',
'Date uploaded' => 'date-posted-desc',
'Date taken' => 'date-taken-desc',
'Interesting' => 'interestingness-desc',
),
'defaultValue' => 'relevance',
)
),
'By username' => array(
@@ -50,60 +29,30 @@ class FlickrBridge extends BridgeAbstract {
'required' => true,
'title' => 'Insert username (as shown in the address bar)',
'exampleValue' => 'flickr'
),
'media' => array(
'name' => 'Media',
'type' => 'list',
'values' => array(
'All (Photos & videos)' => 'all',
'Photos' => 'photos',
'Videos' => 'videos',
),
'defaultValue' => 'all',
),
'sort' => array(
'name' => 'Sort By',
'type' => 'list',
'values' => array(
'Relevance' => 'relevance',
'Date uploaded' => 'date-posted-desc',
'Date taken' => 'date-taken-desc',
'Interesting' => 'interestingness-desc',
),
'defaultValue' => 'date-posted-desc',
)
)
);
private $username = '';
public function collectData() {
public function collectData(){
switch($this->queriedContext) {
case 'Explore':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM($this->getURI())
$html = getSimpleHTMLDOM(self::URI . 'explore')
or returnServerError('Could not request Flickr.');
break;
case 'By keyword':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM($this->getURI())
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
or returnServerError('No results for this query.');
break;
case 'By username':
//$filter = 'photo-models';
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM($this->getURI())
$filter = 'photo-models';
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
or returnServerError('Requested username can\'t be found.');
$this->username = $this->getInput('u');
if ($html->find('span.search-pill-name', 0)) {
$this->username = $html->find('span.search-pill-name', 0)->plaintext;
}
break;
default:
@@ -115,6 +64,7 @@ class FlickrBridge extends BridgeAbstract {
$photo_models = $this->getPhotoModels($model_json, $filter);
foreach($photo_models as $model) {
$item = array();
/* Author name depends on scope. On a keyword search the
@@ -122,12 +72,12 @@ class FlickrBridge extends BridgeAbstract {
* the author is part of the owner data.
*/
if(array_key_exists('username', $model)) {
$item['author'] = urldecode($model['username']);
$item['author'] = $model['username'];
} elseif (array_key_exists('owner', reset($model_json)[0])) {
$item['author'] = urldecode(reset($model_json)[0]['owner']['username']);
$item['author'] = reset($model_json)[0]['owner']['username'];
}
$item['title'] = urldecode((array_key_exists('title', $model) ? $model['title'] : 'Untitled'));
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
$item['uri'] = self::URI . 'photo.gne?id=' . $model['id'];
$description = (array_key_exists('description', $model) ? $model['description'] : '');
@@ -137,7 +87,7 @@ class FlickrBridge extends BridgeAbstract {
. '"><img src="'
. $this->extractContentImage($model)
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
. urldecode($description)
. $description
. '</p>';
$item['enclosures'] = $this->extractEnclosures($model);
@@ -148,46 +98,6 @@ class FlickrBridge extends BridgeAbstract {
}
public function getURI() {
switch($this->queriedContext) {
case 'Explore':
return self::URI . 'explore';
break;
case 'By keyword':
return self::URI . 'search/?q=' . urlencode($this->getInput('q'))
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
break;
case 'By username':
return self::URI . 'search/?user_id=' . urlencode($this->getInput('u'))
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
break;
default:
return parent::getURI();
}
}
public function getName() {
switch($this->queriedContext) {
case 'Explore':
return 'Explore - ' . self::NAME;
break;
case 'By keyword':
return $this->getInput('q') . ' - keyword - ' . self::NAME;
break;
case 'By username':
return $this->username . ' - ' . self::NAME;
break;
default:
return parent::getName();
}
return parent::getName();
}
private function extractJsonModel($html) {
// Find SCRIPT containing JSON data

View File

@@ -1,71 +0,0 @@
<?php
class GenshinImpactBridge extends BridgeAbstract {
const MAINTAINER = 'corenting';
const NAME = 'Genshin Impact';
const URI = 'https://genshin.mihoyo.com/en/news';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'News from the Genshin Impact website';
const PARAMETERS = array(
array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'Latest' => 10,
'Info' => 11,
'Updates' => 12,
'Events' => 13
),
'defaultValue' => 10
)
)
);
public function collectData(){
$category = $this->getInput('category');
$url = 'https://genshin.mihoyo.com/content/yuanshen/getContentList';
$url = $url . '?pageSize=3&pageNum=1&channelId=' . $category;
$api_response = getContents($url)
or returnServerError('Error while downloading the website content');
$json_list = json_decode($api_response, true);
foreach($json_list['data']['list'] as $json_item) {
$article_url = 'https://genshin.mihoyo.com/content/yuanshen/getContent';
$article_url = $article_url . '?contentId=' . $json_item['contentId'];
$article_res = getContents($article_url)
or returnServerError('Error while downloading the website content');
$article_json = json_decode($article_res, true);
$article_time = $article_json['data']['start_time'];
$timezone = 'Asia/Shanghai';
$article_timestamp = new DateTime($article_time, new DateTimeZone($timezone));
$item = array();
$item['title'] = $article_json['data']['title'];
$item['timestamp'] = $article_timestamp->format('U');
$item['content'] = $article_json['data']['content'];
$item['uri'] = $this->getArticleUri($json_item);
$item['id'] = $json_item['contentId'];
// Picture
foreach($article_json['data']['ext'] as $ext) {
if ($ext['arrtName'] == 'banner' && count($ext['value']) == 1) {
$item['enclosures'] = array($ext['value'][0]['url']);
break;
}
}
$this->items[] = $item;
}
}
public function getIcon() {
return 'https://genshin.mihoyo.com/favicon.ico';
}
private function getArticleUri($json_item) {
return 'https://genshin.mihoyo.com/en/news/detail/' . $json_item['contentId'];
}
}

View File

@@ -33,23 +33,17 @@ class GithubIssueBridge extends BridgeAbstract {
)
);
// Allows generalization with GithubPullRequestBridge
const BRIDGE_OPTIONS = array(0 => 'Project Issues', 1 => 'Issue comments');
const URL_PATH = 'issues';
const SEARCH_QUERY_PATH = 'issues';
const SEARCH_QUERY = '?q=is%3Aissue+sort%3Aupdated-desc';
public function getName(){
$name = $this->getInput('u') . '/' . $this->getInput('p');
switch($this->queriedContext) {
case static::BRIDGE_OPTIONS[0]: // Project Issues
case 'Project Issues':
$prefix = static::NAME . 's for ';
if($this->getInput('c')) {
$prefix = static::NAME . 's comments for ';
}
$name = $prefix . $name;
break;
case static::BRIDGE_OPTIONS[1]: // Issue comments
case 'Issue comments':
$name = static::NAME . ' ' . $name . ' #' . $this->getInput('i');
break;
default: return parent::getName();
@@ -57,14 +51,14 @@ class GithubIssueBridge extends BridgeAbstract {
return $name;
}
public function getURI() {
public function getURI(){
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/';
if($this->queriedContext === static::BRIDGE_OPTIONS[1]) {
$uri .= static::URL_PATH . '/' . $this->getInput('i');
} else {
$uri .= static::SEARCH_QUERY_PATH . static::SEARCH_QUERY;
. $this->getInput('p') . '/issues';
if($this->queriedContext === 'Issue comments') {
$uri .= '/' . $this->getInput('i');
} elseif($this->getInput('c')) {
$uri .= '?q=is%3Aissue+sort%3Aupdated-desc';
}
return $uri;
}
@@ -78,19 +72,19 @@ class GithubIssueBridge extends BridgeAbstract {
. $this->getInput('u')
. '/'
. $this->getInput('p')
. '/' . static::URL_PATH . '/'
. '/issues/'
. $issue_number
. '#'
. $comment_id;
}
private function extractIssueEvent($issueNbr, $title, $comment) {
private function extractIssueEvent($issueNbr, $title, $comment){
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
$author = $comment->find('.author, .avatar', 0);
$author = $comment->find('.author', 0);
if ($author) {
$author = trim($author->href, '/');
$author = $author->plaintext;
} else {
$author = '';
}
@@ -101,27 +95,22 @@ class GithubIssueBridge extends BridgeAbstract {
$comment->find('.octicon', 0)->getAttribute('class')
));
$time = $comment->find('relative-time', 0);
if ($time === null) {
return;
}
foreach($comment->find('.Details-content--hidden, .btn') as $el) {
$el->innertext = '';
}
$content = $comment->plaintext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['content'] = $content;
return $item;
}
private function extractIssueComment($issueNbr, $title, $comment) {
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
private function extractIssueComment($issueNbr, $title, $comment){
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->parent->id);
$author = $comment->find('.author', 0)->plaintext;
@@ -129,23 +118,20 @@ class GithubIssueBridge extends BridgeAbstract {
$comment->find('.timeline-comment-header-text', 0)->plaintext
);
$time = $comment->find('relative-time', 0);
if ($time === null) {
return;
}
$content = $comment->find('.comment-body', 0)->innertext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['content'] = $content;
return $item;
}
private function extractIssueComments($issue) {
private function extractIssueComments($issue){
$items = array();
$title = $issue->find('.gh-header-title', 0)->plaintext;
$issueNbr = trim(
@@ -160,45 +146,41 @@ class GithubIssueBridge extends BridgeAbstract {
if ($comment->hasClass('comment')) {
$comment = $comment->parent;
$item = $this->extractIssueComment($issueNbr, $title, $comment);
if ($item !== null) {
$items[] = $item;
}
$items[] = $item;
continue;
} else {
$comment = $comment->parent;
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
if ($item !== null) {
$items[] = $item;
}
$items[] = $item;
}
}
return $items;
}
public function collectData() {
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError(
'No results for ' . static::NAME . ' ' . $this->getURI()
'No results for Github Issue ' . $this->getURI()
);
switch($this->queriedContext) {
case static::BRIDGE_OPTIONS[1]: // Issue comments
case 'Issue comments':
$this->items = $this->extractIssueComments($html);
break;
case static::BRIDGE_OPTIONS[0]: // Project Issues
case 'Project Issues':
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
$info = $issue->find('.opened-by', 0);
preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match);
$issueNbr = $match[1];
$issueNbr = substr(
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
);
$item = array();
$item['content'] = '';
if($this->getInput('c')) {
$uri = static::URI . $this->getInput('u')
. '/' . $this->getInput('p') . '/' . static::URL_PATH . '/' . $issueNbr;
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
if($issue) {
$this->items = array_merge(
@@ -227,7 +209,7 @@ class GithubIssueBridge extends BridgeAbstract {
$item['content'] .= "\n" . 'Comments: ' . $comment_count;
$item['uri'] = self::URI
. trim($issue->find('.js-navigation-open', 0)->getAttribute('href'), '/');
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
$this->items[] = $item;
}
break;
@@ -265,7 +247,7 @@ class GithubIssueBridge extends BridgeAbstract {
$show_comments = 'off';
} break;
case 3: { // Project issues with issue comments
if($path_segments[2] !== static::URL_PATH) {
if($path_segments[2] !== 'issues') {
return null;
}
list($user, $project) = $path_segments;

View File

@@ -1,38 +0,0 @@
<?php
require_once('GithubIssueBridge.php');
class GitHubPullRequestBridge extends GithubIssueBridge {
const MAINTAINER = 'Yaman Qalieh';
const NAME = 'GitHub Pull Request';
const DESCRIPTION = 'Returns the pull request or comments of a pull request of a GitHub project';
const PARAMETERS = array(
'global' => array(
'u' => array(
'name' => 'User name',
'required' => true
),
'p' => array(
'name' => 'Project name',
'required' => true
)
),
'Project Pull Requests' => array(
'c' => array(
'name' => 'Show Pull Request Comments',
'type' => 'checkbox'
)
),
'Pull Request comments' => array(
'i' => array(
'name' => 'Pull Request number',
'type' => 'number',
'required' => true
)
)
);
const BRIDGE_OPTIONS = array(0 => 'Project Pull Requests', 1 => 'Pull Request comments');
const URL_PATH = 'pull';
const SEARCH_QUERY_PATH = 'pulls';
const SEARCH_QUERY = '?q=is%3Apr+sort%3Aupdated-desc';
}

View File

@@ -38,7 +38,7 @@ class GoogleSearchBridge extends BridgeAbstract {
$t = $element->find('a[href]', 0)->href;
$item['uri'] = htmlspecialchars_decode($t);
$item['title'] = $element->find('h3', 0)->plaintext;
$item['content'] = $element->find('span[class=aCOpRe]', 0)->plaintext;
$item['content'] = $element->find('span[class=st]', 0)->plaintext;
$this->items[] = $item;
}

View File

@@ -32,7 +32,7 @@ class HDWallpapersBridge extends BridgeAbstract {
$lastpage = 1;
for($page = 1; $page <= $lastpage; $page++) {
$link = self::URI . $category . '/page/' . $page;
$link = self::URI . '/' . $category . '/page/' . $page;
$html = getSimpleHTMLDOM($link)
or returnServerError('No results for this query.');
@@ -41,16 +41,13 @@ class HDWallpapersBridge extends BridgeAbstract {
$lastpage = min($matches[1], ceil($max / 14));
}
$html = defaultLinkTo($html, self::URI);
foreach($html->find('.wallpapers .wall a') as $element) {
$thumbnail = $element->find('img', 0);
$search = array(self::URI, 'wallpapers.html');
$replace = array(self::URI . 'download/', $this->getInput('r') . '.jpg');
$item = array();
$item['uri'] = str_replace($search, $replace, $element->href);
$item['uri'] = self::URI
. '/download'
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
$item['timestamp'] = time();
$item['title'] = $element->find('em1', 0)->text();
@@ -58,6 +55,7 @@ class HDWallpapersBridge extends BridgeAbstract {
. '<br><a href="'
. $item['uri']
. '"><img src="'
. self::URI
. $thumbnail->src
. '" /></a>';

View File

@@ -1,47 +0,0 @@
<?php
class HackerNewsUserThreadsBridge extends BridgeAbstract {
const MAINTAINER = 'rakoo';
const NAME = 'Hacker News User Threads';
const URI = 'https://news.ycombinator.com';
const CACHE_TIMEOUT = 7200; // 2 hours
const DESCRIPTION = 'Hacker News threads for a user (at https://news.ycombinator.com/threads?id=xxx)';
const PARAMETERS = array( array(
'user' => array(
'name' => 'User',
'type' => 'text',
'required' => true,
'title' => 'User whose threads you want to see'
)
));
public function collectData(){
$url = 'https://news.ycombinator.com/threads?id=' . $this->getInput('user');
$html = getSimpleHTMLDOM($url) or returnServerError('Could not request HN.');
Debug::log('queried ' . $url);
Debug::log('found ' . $html);
$item = array();
$articles = $html->find('tr[class*="comtr"]');
$story = '';
foreach ($articles as $element) {
$id = $element->getAttribute('id');
$item['uri'] = 'https://news.ycombinator.com/item?id=' . $id;
$author = $element->find('span[class*="comhead"]', 0)->find('a[class="hnuser"]', 0)->innertext;
$newstory = $element->find('span[class*="comhead"]', 0)->find('span[class="storyon"]', 0);
if (count($newstory->find('a')) > 0) {
$story = $newstory->find('a', 0)->innertext;
}
$title = $author . ' | on ' . $story;
$item['author'] = $author;
$item['title'] = $title;
$item['timestamp'] = $element->find('span[class*="age"]', 0)->find('a', 0)->innertext;
$item['content'] = $element->find('span[class*="commtext"]', 0)->innertext;
$this->items[] = $item;
}
}
}

View File

@@ -45,10 +45,8 @@ class HeiseBridge extends FeedExpander {
$article = getSimpleHTMLDOMCached($uri)
or returnServerError('Could not open article: ' . $uri);
if ($article) {
$article = defaultLinkTo($article, $uri);
$item = $this->addArticleToItem($item, $article);
}
$article = defaultLinkTo($article, $uri);
$item = $this->addArticleToItem($item, $article);
return $item;
}

View File

@@ -1,114 +0,0 @@
<?php
class IKWYDBridge extends BridgeAbstract {
const MAINTAINER = 'DevonHess';
const NAME = 'I Know What You Download';
const URI = 'https://iknowwhatyoudownload.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns torrent downloads and distributions for an IP address';
const PARAMETERS = array(
array(
'ip' => array(
'name' => 'IP Address',
'required' => true
),
'update' => array(
'name' => 'Update last seen',
'type' => 'checkbox',
'title' => 'Update timestamp every time "last seen" changes'
)
)
);
private $name;
private $uri;
public function detectParameters($url) {
$params = array();
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
$regex .= '(?:en|ru)\/peer\/\?ip=(\d+\.\d+\.\d+\.\d+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['ip'] = urldecode($matches[2]);
return $params;
}
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
$regex .= '(?:(?:en|ru)\/peer\/)?/';
if(preg_match($regex, $url, $matches) > 0) {
$params['ip'] = $_SERVER['REMOTE_ADDR'];
return $params;
}
return null;
}
public function getName() {
if($this->name) {
return $this->name;
} else {
return self::NAME;
}
}
public function getURI() {
if($this->uri) {
return $this->uri;
} else {
return self::URI;
}
}
public function collectData() {
$ip = $this->getInput('ip');
$root = self::URI . 'en/peer/?ip=' . $ip;
$html = getSimpleHTMLDOM($root)
or returnServerError('Could not request ' . self::URI);
$this->name = 'IKWYD: ' . $ip;
$this->uri = $root;
foreach($html->find('.table > tbody > tr') as $download) {
$download = defaultLinkTo($download, self::URI);
$firstSeen = $download->find('.date-column',
0)->innertext;
$lastSeen = $download->find('.date-column',
1)->innertext;
$category = $download->find('.category-column',
0)->innertext;
$torlink = $download->find('.name-column > div > a',
0);
$tortitle = strip_tags($torlink);
$size = $download->find('td', 4)->innertext;
$title = $tortitle;
$author = $ip;
if($this->getInput('update')) {
$timestamp = strtotime($lastSeen);
} else {
$timestamp = strtotime($firstSeen);
}
$uri = $torlink->href;
$content = 'IP address: <a href="' . $root . '">';
$content .= $ip . '</a><br>';
$content .= 'First seen: ' . $firstSeen . '<br>';
$content .= ($this->getInput('update') ? 'Last seen: ' .
$lastSeen . '<br>' : '');
$content .= ($category ? 'Category: ' .
$category . '<br>' : '');
$content .= 'Title: ' . $torlink . '<br>';
$content .= 'Size: ' . $size;
$item = array();
$item['uri'] = $uri;
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
if($category) {
$item['categories'] = array($category);
}
$this->items[] = $item;
}
}
}

View File

@@ -1,7 +1,7 @@
<?php
class InstagramBridge extends BridgeAbstract {
// const MAINTAINER = 'pauder';
const MAINTAINER = 'pauder';
const NAME = 'Instagram Bridge';
const URI = 'https://www.instagram.com/';
const DESCRIPTION = 'Returns the newest images';
@@ -131,7 +131,7 @@ class InstagramBridge extends BridgeAbstract {
switch($media->__typename) {
case 'GraphSidecar':
$data = $this->getInstagramSidecarData($item['uri'], $item['title'], $media, $textContent);
$data = $this->getInstagramSidecarData($item['uri'], $item['title']);
$item['content'] = $data[0];
$item['enclosures'] = $data[1];
break;
@@ -142,7 +142,7 @@ class InstagramBridge extends BridgeAbstract {
$item['enclosures'] = array($mediaURI);
break;
case 'GraphVideo':
$data = $this->getInstagramVideoData($item['uri'], $mediaURI, $media, $textContent);
$data = $this->getInstagramVideoData($item['uri'], $mediaURI);
$item['content'] = $data[0];
if($directLink) {
$item['enclosures'] = $data[1];
@@ -160,7 +160,11 @@ class InstagramBridge extends BridgeAbstract {
}
// returns Sidecar(a post which has multiple media)'s contents and enclosures
protected function getInstagramSidecarData($uri, $postTitle, $mediaInfo, $textContent) {
protected function getInstagramSidecarData($uri, $postTitle) {
$mediaInfo = $this->getSinglePostData($uri);
$textContent = $this->getTextContent($mediaInfo);
$enclosures = array();
$content = '';
foreach($mediaInfo->edge_sidecar_to_children->edges as $singleMedia) {
@@ -183,7 +187,10 @@ class InstagramBridge extends BridgeAbstract {
}
// returns Video post's contents and enclosures
protected function getInstagramVideoData($uri, $mediaURI, $mediaInfo, $textContent) {
protected function getInstagramVideoData($uri, $mediaURI) {
$mediaInfo = $this->getSinglePostData($uri);
$textContent = $this->getTextContent($mediaInfo);
$content = '<video controls>';
$content .= '<source src="' . $mediaInfo->video_url . '" poster="' . $mediaURI . '" type="video/mp4">';
$content .= '<img src="' . $mediaURI . '" alt="">';

View File

@@ -42,6 +42,7 @@ class InternetArchiveBridge extends BridgeAbstract {
$html = defaultLinkTo($html, $this->getURI());
if ($this->getInput('content') !== 'posts') {
$detailsDivNumber = 0;
foreach ($html->find('div.results > div[data-id]') as $index => $result) {
@@ -53,6 +54,7 @@ class InternetArchiveBridge extends BridgeAbstract {
switch($result->class) {
case 'item-ia':
switch($this->getInput('content')) {
case 'reviews':
$item = $this->processReview($result);
@@ -102,6 +104,7 @@ class InternetArchiveBridge extends BridgeAbstract {
public function getName() {
if (!is_null($this->getInput('username')) && !is_null($this->getInput('content'))) {
$contentValues = array_flip(self::PARAMETERS['Account']['content']['values']);
return $contentValues[$this->getInput('content')] . ' - '
@@ -121,10 +124,11 @@ class InternetArchiveBridge extends BridgeAbstract {
}
private function processUpload($result) {
$item = array();
$collection = $result->find('a.stealth', 0);
$collectionLink = $collection->href;
$collectionLink = self::URI . $collection->href;
$collectionTitle = $collection->find('div.item-parent-ttl', 0)->plaintext;
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
@@ -146,6 +150,7 @@ EOD;
}
private function processReview($result) {
$item = array();
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
@@ -167,6 +172,7 @@ EOD;
}
private function processWebArchives($result) {
$item = array();
$item['title'] = trim($result->find('div.ttl', 0)->plaintext);
@@ -183,6 +189,7 @@ EOD;
}
private function processCollection($result) {
$item = array();
$title = trim($result->find('div.collection-title.C.C2', 0)->children(0)->plaintext);
@@ -202,6 +209,7 @@ EOD;
}
private function processHiddenDetails($html, $detailsDivNumber, $item) {
$description = '';
if ($html->find('div.details-ia.hidden-tiles', $detailsDivNumber)) {
@@ -229,6 +237,7 @@ EOD;
}
private function processPosts($html) {
$items = array();
foreach ($html->find('table.forumTable > tr') as $index => $tr) {
@@ -279,7 +288,6 @@ EOD;
break;
}
}
return $items;
}
}

View File

@@ -1,46 +0,0 @@
<?php
class ItchioBridge extends BridgeAbstract {
const NAME = 'itch.io';
const URI = 'https://itch.io';
const DESCRIPTION = 'Fetches the file uploads for a product';
const MAINTAINER = 'jacquesh';
const PARAMETERS = array(array(
'url' => array(
'name' => 'Product URL',
'exampleValue' => 'https://remedybg.itch.io/remedybg',
'required' => true,
)
));
const CACHE_TIMEOUT = 21600; // 6 hours
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$title = $html->find('.game_title', 0)->innertext;
$timestampOriginal = $html->find('span.icon-stopwatch', 0)->parent()->title;
$timestampFormatted = str_replace('@', '', $timestampOriginal);
$content = 'The following files are available to download:<br/>';
foreach ($html->find('div.upload') as $element) {
$filename = $element->find('strong.name', 0)->innertext;
$filesize = $element->find('span.file_size', 0)->first_child()->innertext;
$content = $content . $filename . ' (' . $filesize . ')<br/>';
}
// NOTE: At the time of writing it is not clear under which conditions
// itch updates the timestamp. In case they don't always update it,
// we include the file list as well when computing the UID hash.
$uidContent = $timestampFormatted . $content;
$item = array();
$item['uri'] = $url;
$item['uid'] = $uidContent;
$item['title'] = 'New release for ' . $title;
$item['content'] = $content;
$item['timestamp'] = $timestampFormatted;
$this->items[] = $item;
}
}

View File

@@ -61,8 +61,6 @@ class KernelBugTrackerBridge extends BridgeAbstract {
if($html === false)
returnServerError('Failed to load page!');
$html = defaultLinkTo($html, self::URI);
// Store header information into private members
$this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
$this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
@@ -95,7 +93,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
$item['content'] = str_replace("\n", '<br>', $item['content']);
// Fix relative URIs
$item['content'] = $item['content'];
$item['content'] = $this->replaceRelativeURI($item['content']);
$this->items[] = $item;
}
@@ -127,6 +125,17 @@ class KernelBugTrackerBridge extends BridgeAbstract {
}
}
/**
* Replaces all relative URIs with absolute ones
*
* @param string $content The source string
* @return string Returns the source string with all relative URIs replaced
* by absolute ones.
*/
private function replaceRelativeURI($content){
return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
}
/**
* Adds styles as attributes to tags with known classes
*

View File

@@ -3,7 +3,7 @@ class KoreusBridge extends FeedExpander {
const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'Koreus';
const URI = 'https://www.koreus.com/';
const URI = 'http://www.koreus.com/';
const DESCRIPTION = 'Returns the newest posts from Koreus (full text)';
protected function parseItem($item){
@@ -17,6 +17,6 @@ class KoreusBridge extends FeedExpander {
}
public function collectData(){
$this->collectExpandableDatas('https://feeds.feedburner.com/Koreus-articles');
$this->collectExpandableDatas('http://feeds.feedburner.com/Koreus-articles');
}
}

View File

@@ -352,14 +352,12 @@ class LeBonCoinBridge extends BridgeAbstract {
public function collectData(){
$url = 'https://api.leboncoin.fr/api/adfinder/v1/search';
$url = 'https://api.leboncoin.fr/finder/search/';
$data = $this->buildRequestJson();
$header = array(
'User-Agent: LBC;Android;10;SAMSUNG;phone;0aaaaaaaaaaaaaaa;wifi;8.24.3.8;152437;0',
'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null',
'Content-Type: application/json',
'X-LBC-CC: 7',
'Accept: application/json,application/hal+json',
'Content-Length: ' . strlen($data),
'api_key: ' . self::$LBC_API_KEY
);

View File

@@ -1,73 +0,0 @@
<?php
class MallTvBridge extends BridgeAbstract {
const NAME = 'MALL.TV Bridge';
const URI = 'https://www.mall.tv';
const CACHE_TIMEOUT = 3600;
const DESCRIPTION = 'Return newest videos';
const MAINTAINER = 'kolarcz';
const PARAMETERS = array(
array(
'url' => array(
'name' => 'url to the show',
'required' => true,
'exampleValue' => 'https://www.mall.tv/zivot-je-hra'
)
)
);
private function fixChars($text) {
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
}
private function getUploadTimeFromUrl($url) {
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request MALL.TV detail page');
$scriptLdJson = $html->find('script[type="application/ld+json"]', 0)->innertext;
if (!preg_match('/[\'"]uploadDate[\'"]\s*:\s*[\'"](\d{4}-\d{2}-\d{2})[\'"]/', $scriptLdJson, $match)) {
returnServerError('Could not get date from MALL.TV detail page');
}
return strtotime($match[1]);
}
public function collectData() {
$url = $this->getInput('url');
if (!preg_match('/^https:\/\/www\.mall\.tv\/[a-z0-9-]+(\/[a-z0-9-]+)?\/?$/', $url)) {
returnServerError('Invalid url');
}
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request MALL.TV');
$this->feedUri = $url;
$this->feedName = $this->fixChars($html->find('title', 0)->plaintext);
foreach ($html->find('section.isVideo .video-card') as $element) {
$itemTitle = $element->find('.video-card__details-link', 0);
$itemThumbnail = $element->find('.video-card__thumbnail', 0);
$itemUri = self::URI . $itemTitle->getAttribute('href');
$item = array(
'title' => $this->fixChars($itemTitle->plaintext),
'uri' => $itemUri,
'content' => '<img src="' . $itemThumbnail->getAttribute('data-src') . '" />',
'timestamp' => $this->getUploadTimeFromUrl($itemUri)
);
$this->items[] = $item;
}
}
public function getURI() {
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
}
public function getName() {
return isset($this->feedName) ? $this->feedName : parent::getName();
}
}

View File

@@ -103,8 +103,8 @@ class MarktplaatsBridge extends BridgeAbstract {
$item['content'] .= "<br />\n<br />\n<br />\n" . json_encode($listing);
}
}
$item['content'] .= "<br>\n<br>\nPrice: " . $listing->priceInfo->priceCents / 100;
$item['content'] .= '&nbsp;&nbsp;(' . $listing->priceInfo->priceType . ')';
$item['content'] .= "<br>\n<br>\nPrice: " . $listing->priceInfo->priceCents/100;
$item['content'] .= "&nbsp;&nbsp;(" . $listing->priceInfo->priceType .")";
if(!empty($listing->location->cityName)) {
$item['content'] .= "<br><br>\n" . $listing->location->cityName;
}
@@ -117,11 +117,11 @@ class MarktplaatsBridge extends BridgeAbstract {
}
}
}
public function getName(){
if(!is_null($this->getInput('q'))) {
return $this->getInput('q') . ' - Marktplaats';
}
return parent::getName();
}
if(!is_null($this->getInput('q'))) {
return $this->getInput('q') . ' - Marktplaats';
}
return parent::getName();
}
}

View File

@@ -7,9 +7,9 @@ class MondeDiploBridge extends BridgeAbstract {
const CACHE_TIMEOUT = 21600; //6h
const DESCRIPTION = 'Returns most recent results from MondeDiplo.';
private function cleanText($text) {
return trim(str_replace(array('&nbsp;', '&nbsp'), ' ', $text));
}
private function cleanText($text) {
return trim(str_replace(['&nbsp;', '&nbsp'], ' ', $text));
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
@@ -20,9 +20,9 @@ class MondeDiploBridge extends BridgeAbstract {
$title = $element->find('h3', 0)->plaintext;
$datesAuteurs = $element->find('div.dates_auteurs', 0)->plaintext;
$item = array();
$item['uri'] = urljoin(self::URI, $element->href);
$item['uri'] = self::URI . $element->href;
$item['title'] = $this->cleanText($title) . ' - ' . $this->cleanText($datesAuteurs);
$item['content'] = $this->cleanText(str_replace(array($title, $datesAuteurs), '', $element->plaintext));
$item['content'] = $this->cleanText(str_replace([$title, $datesAuteurs], '', $element->plaintext));
$this->items[] = $item;
}

View File

@@ -24,7 +24,7 @@ class NasaApodBridge extends BridgeAbstract {
$picture_html_string = $picture_html->innertext;
//Extract image and explanation
$image_wrapper = $picture_html->find('a', 1);
$image_wrapper = $picture_html->find('a',1);
$image_path = $image_wrapper->href;
$img_placeholder = $image_wrapper->find('img', 0);
$img_alt = $img_placeholder->alt;

View File

@@ -148,7 +148,7 @@ class NineGagBridge extends BridgeAbstract {
}
if (!$AvoidElement) {
$item['uri'] = preg_replace('/^http:/i', 'https:', $post['url']);
$item['uri'] = $post['url'];
$item['title'] = $post['title'];
$item['content'] = self::getContent($post);
$item['categories'] = self::getCategories($post);

View File

@@ -26,7 +26,7 @@ class NordbayernBridge extends BridgeAbstract {
'Gunzenhausen' => 'gunzenhausen',
'Hersbruck' => 'hersbruck',
'Herzogenaurach' => 'herzogenaurach',
'Hilpoltstein' => 'hilpoltstein',
'Hilpolstein' => 'holpolstein',
'Höchstadt' => 'hoechstadt',
'Lauf' => 'lauf',
'Neumarkt' => 'neumarkt',
@@ -48,16 +48,11 @@ class NordbayernBridge extends BridgeAbstract {
));
private function getImageUrlFromScript($script) {
preg_match(
"#src=\\\\'(https:[-:\\.\\\\/a-zA-Z0-9%_]*\\.(jpg|JPG))#",
$script->innertext,
$matches,
PREG_OFFSET_CAPTURE
);
preg_match("#src=\\\\'(https:[-:\\.\\\\/a-zA-Z0-9%_]*\\.(jpg|JPG))#", $script->innertext, $matches, PREG_OFFSET_CAPTURE);
if(isset($matches[1][0])) {
return stripcslashes($matches[1][0]) . '?w=800';
}
return null;
return null;
}
private function handleArticle($link) {

View File

@@ -1,5 +1,5 @@
<?php
class NyaaTorrentsBridge extends FeedExpander {
class NyaaTorrentsBridge extends BridgeAbstract {
const MAINTAINER = 'ORelio';
const NAME = 'NyaaTorrents';
@@ -50,11 +50,6 @@ class NyaaTorrentsBridge extends FeedExpander {
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
),
'u' => array(
'name' => 'User',
'description' => 'User',
'type' => 'text'
)
)
);
@@ -63,45 +58,76 @@ class NyaaTorrentsBridge extends FeedExpander {
return self::URI . 'static/favicon.png';
}
public function collectData(){
$this->collectExpandableDatas(
self::URI . '?page=rss&s=id&o=desc&'
. http_build_query(array(
'f' => $this->getInput('f'),
'c' => $this->getInput('c'),
'q' => $this->getInput('q'),
'u' => $this->getInput('u')
)), 20);
}
public function collectData() {
protected function parseItem($newItem){
$item = parent::parseItem($newItem);
// Build Search URL from user-provided parameters
$search_url = self::URI . '?s=id&o=desc&'
. http_build_query(array(
'f' => $this->getInput('f'),
'c' => $this->getInput('c'),
'q' => $this->getInput('q')
));
//Convert URI from torrent file to web page
$item['uri'] = str_replace('/download/', '/view/', $item['uri']);
$item['uri'] = str_replace('.torrent', '', $item['uri']);
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url)
or returnServerError('Could not request Nyaa: ' . $search_url);
$links = $html->find('a');
$results = array();
foreach ($links as $link)
if (strpos($link->href, '/view/') === 0 && !in_array($link->href, $results))
$results[] = $link->href;
if (empty($results) && empty($this->getInput('q')))
returnServerError('No results from Nyaa: ' . $url, 500);
if ($item_html = getSimpleHTMLDOMCached($item['uri'])) {
//Process each item individually
foreach ($results as $element) {
//Retrieve full description from page contents
$item_desc = str_get_html(
markdownToHtml(html_entity_decode($item_html->find('#torrent-description', 0)->innertext))
);
//Retrieve image for thumbnail or generic logo fallback
$item_image = $this->getURI() . 'static/img/avatar/default.png';
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
//Limit total amount of requests
if(count($this->items) >= 20) {
break;
}
//Add expanded fields to the current item
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
}
$torrent_id = str_replace('/view/', '', $element);
return $item;
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID
$item_uri = self::URI . 'view/' . $torrent_id;
//Retrieve full description from torrent page
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
//Retrieve data from page contents
$item_title = str_replace(' :: Nyaa', '', $item_html->find('title', 0)->plaintext);
$item_desc = str_get_html(
markdownToHtml(html_entity_decode($item_html->find('#torrent-description', 0)->innertext))
);
$item_author = extractFromDelimiters($item_html->outertext, 'href="/user/', '"');
$item_date = intval(extractFromDelimiters($item_html->outertext, 'data-timestamp="', '"'));
//Retrieve image for thumbnail or generic logo fallback
$item_image = $this->getURI() . 'static/img/avatar/default.png';
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
$this->items[] = $item;
}
}
$element = null;
}
$results = null;
}
}

View File

@@ -1,37 +0,0 @@
<?php
class OpenwrtSecurityBridge extends BridgeAbstract {
const NAME = 'OpenWrt Security Advisories';
const URI = 'https://openwrt.org/advisory/start';
const DESCRIPTION = 'Security Advisories published by openwrt.org';
const MAINTAINER = 'mschwld';
const CACHE_TIMEOUT = 3600;
const WEBROOT = 'https://openwrt.org';
public function collectData() {
$item = array();
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request entries');
$advisories = $html->find('div[class=plugin_nspages]', 0);
foreach($advisories->find('a[class=wikilink1]') as $element) {
$item = array();
$row = $element->innertext;
$item['title'] = substr($row, 0, strpos($row, ' - '));
$item['timestamp'] = $this->getDate($element->href);
$item['uri'] = self::WEBROOT . $element->href;
$item['uid'] = self::WEBROOT . $element->href;
$item['content'] = substr($row, strpos($row, ' - ') + 3);
$item['author'] = 'OpenWrt Project';
$this->items[] = $item;
}
}
private function getDate($href) {
$date = substr($href, -12);
return $date;
}
}

View File

@@ -11,11 +11,13 @@ class OtrkeyFinderBridge extends BridgeAbstract {
'searchterm' => array(
'name' => 'Search term',
'exampleValue' => 'Terminator',
'defaultValue' => '',
'title' => 'The search term is case-insensitive',
),
'station' => array(
'name' => 'Station name',
'exampleValue' => 'ARD',
'defaultValue' => '',
),
'type' => array(
'name' => 'Media type',
@@ -141,11 +143,7 @@ class OtrkeyFinderBridge extends BridgeAbstract {
$item['content'] = $content;
if (preg_match(self::TIME_REGEX, $file, $matches) === 1) {
$item['timestamp'] = DateTime::createFromFormat(
'y.m.d_H-i',
$matches[0],
new DateTimeZone('Europe/Berlin')
)->getTimestamp();
$item['timestamp'] = DateTime::createFromFormat('y.m.d_H-i', $matches[0], new DateTimeZone('Europe/Berlin'))->getTimestamp();
}
return $item;

View File

@@ -123,25 +123,17 @@ class PikabuBridge extends BridgeAbstract {
}
}
$title_element = $post->find('.story__title-link', 0);
$title = $title_element->plaintext;
$community_link = $post->find('.story__community-link', 0);
// adding special marker for "Maybe News" section
// these posts are fake
if (!is_null($community_link) && $community_link->getAttribute('href') == '/community/maybenews') {
$title = '[' . $community_link->innertext . '] ' . $title;
}
$title = $post->find('.story__title-link', 0);
$item = array();
$item['categories'] = $categories;
$item['author'] = $post->find('.user__nick', 0)->innertext;
$item['title'] = $title;
$item['title'] = $title->plaintext;
$item['content'] = strip_tags(
backgroundToImg($post->find('.story__content-inner', 0)->innertext),
'<br><p><img><a>
');
$item['uri'] = $title_element->href;
$item['uri'] = $title->href;
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$this->items[] = $item;
}

View File

@@ -1,45 +0,0 @@
<?php
class PresidenciaPTBridge extends BridgeAbstract {
const NAME = 'Presidência da República Portuguesa';
const URI = 'https://www.presidencia.pt';
const DESCRIPTION = 'Presidência da República Portuguesa | Mensagens';
const MAINTAINER = 'somini';
const PT_MONTH_NAMES = array(
'janeiro',
'fevereiro',
'março',
'abril',
'maio',
'junho',
'julho',
'agosto',
'setembro',
'outubro',
'novembro',
'dezembro');
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/atualidade/mensagens')
or returnServerError('Could not load content');
foreach($html->find('#atualidade-list article.card-block') as $element) {
$item = array();
$link = $element->find('a', 0);
$etitle = $link->find('h2', 0);
$edts = $element->find('p', 1);
$edt = html_entity_decode($edts->innertext, ENT_HTML5);
$item['title'] = $etitle->innertext;
$item['uri'] = self::URI . $link->href;
$item['description'] = $element;
$item['timestamp'] = str_ireplace(
array_map(function($name) { return ' de ' . $name . ' de '; }, self::PT_MONTH_NAMES),
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
$edt);
$this->items[] = $item;
}
}
}

View File

@@ -1,56 +0,0 @@
<?php
class RaceDepartmentBridge extends FeedExpander {
const NAME = 'RaceDepartment News';
const URI = 'https://racedepartment.com/';
const DESCRIPTION = 'Get the latest (sim)racing news from RaceDepartment.';
const MAINTAINER = 't0stiman';
public function collectData() {
$this->collectExpandableDatas('https://www.racedepartment.com/news/archive.rss', 10);
}
protected function parseItem($feedItem) {
$item = parent::parseItem($feedItem);
//fetch page
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
or returnServerError('Could not retrieve ' . $feedItem->link);
//extract article
$item['content'] = $articlePage->find('div.thfeature_firstPost', 0);
//convert iframes to links. meant for embedded videos.
foreach($item['content']->find('iframe') as $found) {
$iframeUrl = $found->getAttribute('src');
if ($iframeUrl) {
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
}
}
//get rid of some elements we don't need
$to_remove_selectors = array(
'div.p-title', //title
'ul.listInline', //Thread starter, Start date
'div.rd_news_article_share_buttons',
'div.thfeature_firstPost-author',
'div.reactionsBar',
'footer',
'div.message-lastEdit',
'section.message-attachments'
);
foreach($to_remove_selectors as $selector) {
foreach($item['content']->find($selector) as $found) {
$found->outertext = '';
}
}
//category
$forumPath = $articlePage->find('div.breadcrumb', 0);
$pathElements = $forumPath->find('span');
$item['categories'] = array(end($pathElements)->innertext);
return $item;
}
}

View File

@@ -25,7 +25,7 @@ class RadioMelodieBridge extends BridgeAbstract {
$picture = array();
// Get the Main picture URL
$picture[] = self::URI . $article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src;
$picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
$audioHTML = $article->find('audio');
// Add the audio element to the enclosure

View File

@@ -1,22 +1,12 @@
<?php
class RedditBridge extends FeedExpander {
class RedditBridge extends BridgeAbstract {
const MAINTAINER = 'dawidsowa';
const MAINTAINER = 'leomaradan';
const NAME = 'Reddit Bridge';
const URI = 'https://www.reddit.com';
const DESCRIPTION = 'Return hot submissions from Reddit';
const URI = 'https://www.reddit.com/';
const DESCRIPTION = 'Reddit RSS Feed fixer';
const PARAMETERS = array(
'global' => array(
'score' => array(
'name' => 'Minimal score',
'required' => false,
'type' => 'number',
'exampleValue' => 100,
'title' => 'Filter out posts with lower score'
)
),
'single' => array(
'r' => array(
'name' => 'SubReddit',
@@ -32,221 +22,19 @@ class RedditBridge extends BridgeAbstract {
'exampleValue' => 'selfhosted, php',
'title' => 'SubReddit names, separated by commas'
)
),
'user' => array(
'u' => array(
'name' => 'User',
'required' => true,
'title' => 'User name'
),
'comments' => array(
'type' => 'checkbox',
'name' => 'Comments',
'title' => 'Whether to return comments',
'defaultValue' => false
)
)
);
public function detectParameters($url) {
$parsed_url = parse_url($url);
public function collectData(){
if ($parsed_url['host'] != 'www.reddit.com' && $parsed_url['host'] != 'old.reddit.com') return null;
$path = explode('/', $parsed_url['path']);
if ($path[1] == 'r') {
return array(
'r' => $path[2]
);
} elseif ($path[1] == 'user') {
return array(
'u' => $path[2]
);
} else {
return null;
}
switch($this->queriedContext) {
case 'single': $subreddits[] = $this->getInput('r'); break;
case 'multi': $subreddits = explode(',', $this->getInput('rs')); break;
}
public function getIcon() {
return 'https://www.redditstatic.com/desktop2x/img/favicon/favicon-96x96.png';
}
public function getName() {
if ($this->queriedContext == 'single') {
return 'Reddit r/' . $this->getInput('r');
} elseif ($this->queriedContext == 'user') {
return 'Reddit u/' . $this->getInput('u');
} else {
return self::NAME;
}
}
public function collectData() {
$user = false;
$comments = false;
switch ($this->queriedContext) {
case 'single':
$subreddits[] = $this->getInput('r');
break;
case 'multi':
$subreddits = explode(',', $this->getInput('rs'));
break;
case 'user':
$subreddits[] = $this->getInput('u');
$user = true;
$comments = $this->getInput('comments');
break;
}
foreach ($subreddits as $subreddit) {
$name = trim($subreddit);
$values = getContents(self::URI . ($user ? '/user/' : '/r/') . $name . '.json')
or returnServerError('Unable to fetch posts!');
$decodedValues = json_decode($values);
foreach ($decodedValues->data->children as $post) {
if ($post->kind == 't1' && !$comments) {
continue;
}
$data = $post->data;
if ($data->score < $this->getInput('score')) {
continue;
}
$item = array();
$item['author'] = $data->author;
$item['uid'] = $data->id;
$item['timestamp'] = $data->created_utc;
$item['uri'] = $this->encodePermalink($data->permalink);
$item['categories'] = array();
if ($post->kind == 't1') {
$item['title'] = 'Comment: ' . $data->link_title;
} else {
$item['title'] = $data->title;
$item['categories'][] = $data->link_flair_text;
$item['categories'][] = $data->pinned ? 'Pinned' : null;
$item['categories'][] = $data->spoiler ? 'Spoiler' : null;
}
$item['categories'][] = $data->over_18 ? 'NSFW' : null;
$item['categories'] = array_filter($item['categories']);
if ($post->kind == 't1') {
// Comment
$item['content']
= htmlspecialchars_decode($data->body_html);
} elseif ($data->is_self) {
// Text post
$item['content']
= htmlspecialchars_decode($data->selftext_html);
} elseif (isset($data->post_hint) ? $data->post_hint == 'link' : false) {
// Link with preview
if (isset($data->media)) {
// Reddit embeds content for some sites (e.g. Twitter)
$embed = htmlspecialchars_decode(
$data->media->oembed->html
);
} else {
$embed = '';
}
$item['content'] = $this->template(
$data->url,
$data->thumbnail,
$data->domain
) . $embed;
} elseif (isset($data->post_hint) ? $data->post_hint == 'image' : false) {
// Single image
$item['content'] = $this->link(
$this->encodePermalink($data->permalink),
'<img src="' . $data->url . '" />'
);
} elseif (isset($data->is_gallery) ? $data->is_gallery : false) {
// Multiple images
$images = array();
foreach ($data->gallery_data->items as $media) {
$id = $media->media_id;
$type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u';
$src = $data->media_metadata->$id->s->$type;
$images[] = '<figure><img src="' . $src . '"/></figure>';
}
$item['content'] = implode('', $images);
} elseif ($data->is_video) {
// Video
// Higher index -> Higher resolution
end($data->preview->images[0]->resolutions);
$index = key($data->preview->images[0]->resolutions);
$item['content'] = $this->template(
$data->url,
$data->preview->images[0]->resolutions[$index]->url,
'Video'
);
} elseif (isset($data->media) ? $data->media->type == 'youtube.com' : false) {
// Youtube link
$item['content'] = $this->template(
$data->url,
$data->media->oembed->thumbnail_url,
'YouTube');
} elseif (explode('.', $data->domain)[0] == 'self') {
// Crossposted text post
// TODO (optionally?) Fetch content of the original post.
$item['content'] = $this->link(
$this->encodePermalink($data->permalink),
'Crossposted from r/'
. explode('.', $data->domain)[1]
);
} else {
// Link WITHOUT preview
$item['content'] = $this->link($data->url, $data->domain);
}
$this->items[] = $item;
}
$this->collectExpandableDatas("https://www.reddit.com/r/$name/.rss");
}
}
private function encodePermalink($link) {
return self::URI . implode(
'/',
array_map('urlencode', explode('/', $link))
);
}
private function template($href, $src, $caption) {
return '<a href="' . $href . '"><figure><figcaption>'
. $caption . '</figcaption><img src="'
. $src . '"/></figure></a>';
}
private function link($href, $text) {
return '<a href="' . $href . '">' . $text . '</a>';
}
}

View File

@@ -1,7 +1,7 @@
<?php
// This bridge depends on Releases3DSBridge
if (!class_exists('Releases3DSBridge')) {
if (!class_exists('Releases3DSBridge')){
include('Releases3DSBridge.php');
}

View File

@@ -8,7 +8,6 @@ class ReporterreBridge extends BridgeAbstract {
private function extractContent($url){
$html2 = getSimpleHTMLDOM($url);
$html2 = defaultLinkTo($html2, self::URI);
foreach($html2->find('div[style=text-align:justify]') as $e) {
$text = $e->outertext;
@@ -17,6 +16,13 @@ class ReporterreBridge extends BridgeAbstract {
$html2->clear();
unset($html2);
// Replace all relative urls with absolute ones
$text = preg_replace(
'/(href|src)(\=[\"\'])(?!http)([^"\']+)/ims',
'$1$2' . self::URI . '$3',
$text
);
$text = strip_tags($text, '<p><br><a><img>');
return $text;
}

View File

@@ -1,261 +0,0 @@
<?php
class ReutersBridge extends BridgeAbstract
{
const MAINTAINER = 'hollowleviathan, spraynard, csisoap';
const NAME = 'Reuters Bridge';
const URI = 'https://reuters.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns news from Reuters';
private $feedName = self::NAME;
/**
* Wireitem types allowed in the final story output
*/
const ALLOWED_WIREITEM_TYPES = array(
'story',
'headlines'
);
/**
* Wireitem template types allowed in the final story output
*/
const ALLOWED_TEMPLATE_TYPES = array(
'story',
'headlines'
);
const PARAMETERS = array(
array(
'feed' => array(
'name' => 'News Feed',
'type' => 'list',
'title' => 'Feeds from Reuters U.S/International edition',
'values' => array(
'Aerospace and Defense' => 'aerospace',
'Business' => 'business',
'China' => 'china',
'Energy' => 'energy',
'Entertainment' => 'chan:8ym8q8dl',
'Environment' => 'chan:6u4f0jgs',
'Health' => 'chan:8hw7807a',
'Lifestyle' => 'life',
'Markets' => 'markets',
'Politics' => 'politics',
'Science' => 'science',
'Special Reports' => 'special-reports',
'Sports' => 'sports',
'Tech' => 'tech',
'Top News' => 'home/topnews',
'UK' => 'chan:61leiu7j',
'USA News' => 'us',
'Wire' => 'wire',
'World' => 'world',
)
)
)
);
/**
* Performs an HTTP request to the Reuters API and returns decoded JSON
* in the form of an associative array
* @param string $feed_uri Parameter string to the Reuters API
* @return array
*/
private function getJson($feed_uri)
{
$uri = "https://wireapi.reuters.com/v8$feed_uri";
$returned_data = getContents($uri);
return json_decode($returned_data, true);
}
/**
* Takes in data from Reuters Wire API and
* creates structured data in the form of a list
* of story information.
* @param array $data JSON collected from the Reuters Wire API
*/
private function processData($data)
{
/**
* Gets a list of wire items which are groups of templates
*/
$reuters_allowed_wireitems = array_filter(
$data, function ($wireitem) {
return in_array(
$wireitem['wireitem_type'],
self::ALLOWED_WIREITEM_TYPES
);
}
);
/*
* Gets a list of "Templates", which is data containing a story
*/
$reuters_wireitem_templates = array_reduce(
$reuters_allowed_wireitems,
function (array $carry, array $wireitem) {
$wireitem_templates = $wireitem['templates'];
return array_merge(
$carry,
array_filter(
$wireitem_templates, function (
array $template_data
) {
return in_array(
$template_data['type'],
self::ALLOWED_TEMPLATE_TYPES
);
}
)
);
},
array()
);
return $reuters_wireitem_templates;
}
private function getArticle($feed_uri)
{
// This will make another request to API to get full detail of article and author's name.
$rawData = $this->getJson($feed_uri);
$reuters_wireitems = $rawData['wireitems'];
$processedData = $this->processData($reuters_wireitems);
$first = reset($processedData);
$article_content = $first['story']['body_items'];
$authorlist = $first['story']['authors'];
$category = $first['story']['channel']['name'];
$image_list = $first['story']['images'];
$content_detail = array(
'content' => $this->handleArticleContent($article_content),
'author' => $this->handleAuthorName($authorlist),
'category' => $category,
'images' => $this->handleImage($image_list),
);
return $content_detail;
}
private function handleImage($images) {
$img_placeholder = '';
foreach($images as $image) { // Add more image to article.
$image_url = $image['url'];
$image_caption = $image['caption'];
$img = "<img src=\"$image_url\">";
$img_caption = "<figcaption style=\"text-align: center;\"><i>$image_caption</i></figcaption>";
$figure = "<figure>$img \t $img_caption</figure>";
$img_placeholder = $img_placeholder . $figure;
}
return $img_placeholder;
}
private function handleAuthorName($authors) {
$author = '';
$counter = 0;
foreach ($authors as $data) {
//Formatting author's name.
$counter++;
$name = $data['name'];
if ($counter == count($authors)) {
$author = $author . $name;
} else {
$author = $author . "$name, ";
}
}
return $author;
}
private function handleArticleContent($contents) {
$description = '';
foreach ($contents as $content) {
$data;
if(isset($content['content'])) {
$data = $content['content'];
}
switch($content['type']) {
case 'paragraph':
$description = $description . "<p>$data</p>";
break;
case 'heading':
$description = $description . "<h3>$data</h3>";
break;
case 'infographics':
$description = $description . "<img src=\"$data\">";
break;
case 'inline_items':
$item_list = $content['items'];
$description = $description . '<p>';
foreach ($item_list as $item) {
if($item['type'] == 'text') {
$description = $description . $item['content'];
} else {
$description = $description . $item['symbol'];
}
}
$description = $description . '</p>';
break;
case 'p_table':
$description = $description . $content['content'];
break;
}
}
return $description;
}
public function getName() {
return $this->feedName;
}
public function collectData()
{
$reuters_feed_name = $this->getInput('feed');
if(strpos($reuters_feed_name, 'chan:') !== false) {
// Now checking whether that feed has unique ID or not.
$feed_uri = "/feed/rapp/us/wirefeed/$reuters_feed_name";
} else {
$feed_uri = "/feed/rapp/us/tabbar/feeds/$reuters_feed_name";
}
$data = $this->getJson($feed_uri);
$reuters_wireitems = $data['wireitems'];
$this->feedName = $data['wire_name'] . ' | Reuters';
$processedData = $this->processData($reuters_wireitems);
// Merge all articles from Editor's Highlight section into existing array of templates.
$top_section = reset($processedData);
if ($top_section['type'] == 'headlines') {
$top_section = array_shift($processedData);
$articles = $top_section['headlines'];
$processedData = array_merge($articles, $processedData);
}
foreach ($processedData as $story) {
$item['uid'] = $story['story']['usn'];
$article_uri = $story['template_action']['api_path'];
$content_detail = $this->getArticle($article_uri);
$description = $content_detail['content'];
$author = $content_detail['author'];
$images = $content_detail['images'];
$item['categories'] = array($content_detail['category']);
$item['author'] = $author;
if (!(bool) $description) {
$description = $story['story']['lede']; // Just in case the content doesn't have anything.
} else {
$item['content'] = "$description $images";
}
$item['title'] = $story['story']['hed'];
$item['timestamp'] = $story['story']['updated_at'];
$item['uri'] = $story['template_action']['url'];
$this->items[] = $item;
}
}
}

View File

@@ -8,14 +8,12 @@ class Rule34pahealBridge extends Shimmie2Bridge {
const URI = 'https://rule34.paheal.net/';
const DESCRIPTION = 'Returns images from given page';
const PATHTODATA = '.shm-thumb';
protected function getItemFromElement($element){
$item = array();
$item['uri'] = rtrim($this->getURI(), '/') . $element->find('.shm-thumb-link', 0)->href;
$item['uri'] = $this->getURI() . $element->href;
$item['id'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
$item['timestamp'] = time();
$thumbnailUri = $element->find('a', 1)->href;
$thumbnailUri = $element->find('img', 0)->src;
$item['tags'] = $element->getAttribute('data-tags');
$item['title'] = $this->getName() . ' | ' . $item['id'];
$item['content'] = '<a href="'

View File

@@ -1,91 +0,0 @@
<?php
class SeznamZpravyBridge extends BridgeAbstract {
const NAME = 'Seznam Zprávy Bridge';
const URI = 'https://seznamzpravy.cz';
const DESCRIPTION = 'Returns newest stories from Seznam Zprávy';
const MAINTAINER = 'thezeroalpha';
const PARAMETERS = array(
'By Author' => array(
'author' => array(
'name' => 'Author String',
'type' => 'text',
'required' => true,
'title' => 'The dash-separated author string, as shown in the URL bar.',
'pattern' => '[a-z]+-[a-z]+-[0-9]+',
'exampleValue' => 'janek-rubes-506'
),
)
);
private $feedName;
public function getName() {
if (isset($this->feedName)) {
return $this->feedName;
}
return parent::getName();
}
public function collectData() {
$ONE_DAY = 86500;
switch($this->queriedContext) {
case 'By Author':
$url = 'https://www.seznamzpravy.cz/autor/';
$selectors = array(
'breadcrumbs' => 'div[data-dot=ogm-breadcrumb-navigation]',
'article_list' => 'ul.ogm-document-timeline-page.atm-list-ul li article[data-dot=mol-timeline-item]',
'article_title' => 'a[data-dot=mol-article-card-title]',
'article_dm' => 'span.mol-formatted-date__date',
'article_time' => 'span.mol-formatted-date__time',
'article_content' => 'div[data-dot=ogm-article-content]'
);
$html = getSimpleHTMLDOMCached($url . $this->getInput('author'), $ONE_DAY);
$main_breadcrumbs = $html->find($selectors['breadcrumbs'], 0);
$author = $main_breadcrumbs->last_child()->plaintext
or returnServerError('Could not get author on: ' . $this->getURI());
$this->feedName = $author . ' - Seznam Zprávy';
$articles = $html->find($selectors['article_list'])
or returnServerError('Could not find articles on: ' . $this->getURI());
foreach ($articles as $article) {
$title_link = $article->find($selectors['article_title'], 0)
or returnServerError('Could not find title on: ' . $this->getURI());
$article_url = $title_link->href;
$article_content_html = getSimpleHTMLDOMCached($article_url, $ONE_DAY);
$content_e = $article_content_html->find($selectors['article_content'], 0);
$content_text = $content_e->innertext
or returnServerError('Could not get article content for: ' . $article_url);
$breadcrumbs_e = $article_content_html->find($selectors['breadcrumbs'], 0);
$breadcrumbs = $breadcrumbs_e->children();
$num_breadcrumbs = count($breadcrumbs);
$categories = array();
foreach ($breadcrumbs as $cat) {
if (--$num_breadcrumbs <= 0) {
break;
}
$categories[] = trim($cat->plaintext);
}
$article_dm_e = $article->find($selectors['article_dm'], 0);
$article_dm_text = $article_dm_e->plaintext;
$article_dmy = preg_replace('/[^0-9\.]/', '', $article_dm_text) . date('Y');
$article_time = $article->find($selectors['article_time'], 0)->plaintext;
$item = array(
'title' => $title_link->plaintext,
'uri' => $title_link->href,
'timestamp' => strtotime($article_dmy . ' ' . $article_time),
'author' => $author,
'content' => $content_text,
'categories' => $categories
);
$this->items[] = $item;
}
break;
}
$this->items[] = $item;
}
}

View File

@@ -455,35 +455,6 @@ class SkimfeedBridge extends BridgeAbstract {
}
public function detectParameters($url) {
if (0 !== strpos($url, static::URI)) {
return null;
}
foreach(self::PARAMETERS as $channels) {
foreach($channels as $box_name => $box) {
foreach($box['values'] as $name => $channel_url) {
if (static::URI . $channel_url === $url) {
return array(
$box_name => $name,
);
}
}
}
}
return null;
}
public function getName() {
switch($this->queriedContext) {

View File

@@ -27,9 +27,6 @@ class SoundCloudBridge extends BridgeAbstract {
private $feedIcon = null;
private $clientIDCache = null;
private $clientIdRegex = '/client_id.*?"(.+?)"/';
private $widgetRegex = '/widget-.+?\.js/';
public function collectData(){
$res = $this->apiGet('resolve', array(
'url' => 'https://soundcloud.com/' . $this->getInput('u')
@@ -115,32 +112,21 @@ class SoundCloudBridge extends BridgeAbstract {
// Without url=http, this returns a 404
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
or returnServerError('Unable to get player page.');
// Extract widget JS filenames from player page
if(preg_match_all($this->widgetRegex, $playerHTML, $matches) == false)
or returnServerError('Unable to get player page.');
$regex = '/widget-.+?\.js/';
if(preg_match($regex, $playerHTML, $matches) == false)
returnServerError('Unable to find widget JS URL.');
$widgetURL = 'https://widget.sndcdn.com/' . $matches[0];
$clientID = '';
// Loop widget js files and extract client ID
foreach ($matches[0] as $widgetFile) {
$widgetURL = 'https://widget.sndcdn.com/' . $widgetFile;
$widgetJS = getContents($widgetURL)
or returnServerError('Unable to get widget JS page.');
if(preg_match($this->clientIdRegex, $widgetJS, $matches)) {
$clientID = $matches[1];
$this->clientIDCache->saveData($clientID);
return $clientID;
}
}
if (empty($clientID)) {
$widgetJS = getContents($widgetURL)
or returnServerError('Unable to get widget JS page.');
$regex = '/client_id.*?"(.+?)"/';
if(preg_match($regex, $widgetJS, $matches) == false)
returnServerError('Unable to find client ID.');
}
$clientID = $matches[1];
$this->clientIDCache->saveData($clientID);
return $clientID;
}
private function buildAPIURL($endpoint, $parameters){

View File

@@ -1,34 +0,0 @@
<?php
class SymfonyCastsBridge extends BridgeAbstract {
const NAME = 'SymfonyCasts Bridge';
const URI = 'https://symfonycasts.com/';
const DESCRIPTION = 'Follow new updates on symfonycasts.com';
const MAINTAINER = 'Park0';
const CACHE_TIMEOUT = 3600;
public function collectData() {
$html = getSimpleHTMLDOM('https://symfonycasts.com/updates/find')
or returnServerError('Unable to get page.');
$dives = $html->find('div');
/* @var simple_html_dom $div */
foreach ($dives as $div) {
$id = $div->getAttribute('data-mark-update-id-value');
$type = $div->find('h5', 0);
$title = $div->find('span', 0);
$dateString = $div->find('h5.font-gray', 0);
$href = $div->find('a', 0);
$url = 'https://symfonycasts.com' . $href->getAttribute('href');
$item = array(); // Create an empty item
$item['uid'] = $id;
$item['title'] = $title->innertext;
$item['timestamp'] = $dateString->innertext;
$item['content'] = $type->plaintext . '<a href="' . $url . '">' . $title . '</a>';
$item['uri'] = $url;
$this->items[] = $item; // Add item to the list
}
}
}

View File

@@ -21,18 +21,6 @@ class TelegramBridge extends BridgeAbstract {
private $itemTitle = '';
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
private $detectParamsRegex = '/^https?:\/\/t.me\/(?:s\/)?([\w]+)$/';
public function detectParameters($url) {
$params = array();
if(preg_match($this->detectParamsRegex, $url, $matches) > 0) {
$params['username'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
@@ -51,8 +39,8 @@ class TelegramBridge extends BridgeAbstract {
$item = array();
$item['uri'] = $this->processUri($messageDiv);
$item['content'] = $this->processContent($messageDiv);
$item['title'] = $this->itemTitle;
$item['content'] = html_entity_decode($this->processContent($messageDiv), ENT_QUOTES);
$item['title'] = html_entity_decode($this->itemTitle, ENT_QUOTES);
$item['timestamp'] = $this->processDate($messageDiv);
$item['enclosures'] = $this->enclosures;
$author = trim($messageDiv->find('a.tgme_widget_message_owner_name', 0)->plaintext);
@@ -132,12 +120,6 @@ class TelegramBridge extends BridgeAbstract {
$messageDiv->find('div.tgme_widget_message_text.js-message_text', 0)->plaintext
);
}
if ($messageDiv->find('div.tgme_widget_message_document', 0)) {
$message .= 'Attachments:';
foreach ($messageDiv->find('div.tgme_widget_message_document') as $attachments) {
$message .= $attachments->find('div.tgme_widget_message_document_title.accent_color', 0);
}
}
if ($messageDiv->find('a.tgme_widget_message_link_preview', 0)) {
$message .= $this->processLinkPreview($messageDiv);

View File

@@ -1,51 +0,0 @@
<?php
class TheFarSideBridge extends BridgeAbstract {
const NAME = 'The Far Side Bridge';
const URI = 'https://www.thefarside.com';
const DESCRIPTION = 'Returns the daily dose';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
const CACHE_TIMEOUT = 3600; // 1 hour
public function collectData() {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request: ' . self::URI);
$div = $html->find('div.tfs-page-container__cows', 0);
$item = array();
$item['uri'] = $html->find('meta[property="og:url"]', 0)->content;
$item['title'] = $div->find('h3', 0)->innertext;
$item['timestamp'] = $div->find('h3', 0)->innertext;
$item['content'] = '';
foreach($div->find('div.card-body') as $index => $card) {
$image = $card->find('img', 0);
$imageUrl = $image->attr['data-src'];
// Images are downloaded to bypass the hotlink protection.
$image = getContents($imageUrl, array('Referer: ' . self::URI))
or returnServerError('Could not request: ' . $imageUrl);
// Encode image as base64
$imageBase64 = base64_encode($image);
$caption = '';
if ($card->find('figcaption', 0)) {
$caption = $card->find('figcaption', 0)->innertext;
}
$item['content'] .= <<<EOD
<figure>
<img title="{$caption}" src="data:image/jpeg;base64,{$imageBase64}"/>
<figcaption>{$caption}</figcaption>
</figure>
<br/>
EOD;
}
$this->items[] = $item;
}
}

View File

@@ -12,7 +12,7 @@ class TheYeteeBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request The Yetee.');
$div = $html->find('.module_timed-item.is--full');
$div = $html->find('.hero-col');
foreach($div as $element) {
$item = array();
@@ -21,15 +21,16 @@ class TheYeteeBridge extends BridgeAbstract {
$title = $element->find('h2', 0)->plaintext;
$item['title'] = $title;
$author = trim($element->find('.module_timed-item--artist a', 0)->plaintext);
$author = trim($element->find('div[class=credit]', 0)->plaintext);
$item['author'] = $author;
$item['uri'] = static::URI;
$uri = $element->find('div[class=controls] a', 0)->href;
$item['uri'] = static::URI . $uri;
$content = '<p>' . $title . ' by ' . $author . '</p>';
$photos = $element->find('a.img');
$content = '<p>' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '</p>';
$photos = $element->find('a[class=js-modaal-gallery] img');
foreach($photos as $photo) {
$content = $content . "<br /><img src='$photo->href' />";
$content = $content . "<br /><img src='$photo->src' />";
$item['enclosures'][] = $photo->src;
}
$item['content'] = $content;

View File

@@ -1,155 +0,0 @@
<?php
class TwitScoopBridge extends BridgeAbstract {
const NAME = 'TwitScoop Bridge';
const URI = 'https://www.twitscoop.com';
const DESCRIPTION = 'Returns trending Twitter topics by country';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
array(
'country' => array(
'name' => 'Country',
'type' => 'list',
'values' => array(
'Worldwide' => 'worldwide',
'Algeria' => 'algeria',
'Argentina' => 'argentina',
'Australia' => 'australia',
'Austria' => 'austria',
'Bahrain' => 'bahrain',
'Belarus' => 'belarus',
'Belgium' => 'belgium',
'Brazil' => 'brazil',
'Canada' => 'canada',
'Chile' => 'chile',
'Colombia' => 'colombia',
'Denmark' => 'denmark',
'Dominican Republic' => 'dominican-republic',
'Ecuador' => 'ecuador',
'Egypt' => 'egypt',
'France' => 'france',
'Germany' => 'germany',
'Ghana' => 'ghana',
'Greece' => 'greece',
'Guatemala' => 'guatemala',
'India' => 'india',
'Indonesia' => 'indonesia',
'Ireland' => 'ireland',
'Israel' => 'israel',
'Italy' => 'italy',
'Japan' => 'japan',
'Jordan' => 'jordan',
'Kenya' => 'kenya',
'Korea' => 'korea',
'Kuwait' => 'kuwait',
'Latvia' => 'latvia',
'Lebanon' => 'lebanon',
'Malaysia' => 'malaysia',
'Mexico' => 'mexico',
'Netherlands' => 'netherlands',
'New Zealand' => 'new-zealand',
'Nigeria' => 'nigeria',
'Norway' => 'norway',
'Oman' => 'oman',
'Pakistan' => 'pakistan',
'Panama' => 'panama',
'Peru' => 'peru',
'Philippines' => 'philippines',
'Poland' => 'poland',
'Portugal' => 'portugal',
'Puerto Rico' => 'puerto-rico',
'Qatar' => 'qatar',
'Russia' => 'russia',
'Saudi Arabia' => 'saudi-arabia',
'Singapore' => 'singapore',
'South Africa' => 'south-africa',
'Spain' => 'spain',
'Sweden' => 'sweden',
'Switzerland' => 'switzerland',
'Thailand' => 'thailand',
'Turkey' => 'turkey',
'Ukraine' => 'ukraine',
'United Arab Emirates' => 'united-arab-emirates',
'United Kingdom' => 'united-kingdom',
'United States' => 'united-states',
'Venezuela' => 'venezuela',
'Vietnam' => 'vietnam',
)
),
'limit' => array(
'name' => 'Topics',
'type' => 'number',
'title' => 'Number of trending topics to return. Max 50',
'defaultValue' => 20,
)
)
);
const CACHE_TIMEOUT = 900; // 15 mins
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$updated = $html->find('time', 0)->datetime;
$trends = $html->find('div.trends', 0);
$limit = $this->getInput('limit');
if ($limit > 50 || $limit < 1) {
$limit = 50;
}
foreach($trends->find('ol.items > li') as $index => $li) {
$number = $index + 1;
$item = array();
$name = rtrim($li->find('span.trend.name', 0)->plaintext, '&nbsp');
$tweets = str_replace(' tweets', '', $li->find('span.tweets', 0)->plaintext);
$tweets = str_replace('<', '', $tweets);
$item['title'] = '#' . $number . ' - ' . $name . ' (' . $tweets . ' tweets)';
$item['uri'] = 'https://twitter.com/search?q=' . rawurlencode($name);
if ($tweets === '10K') {
$tweets = 'less than 10K';
}
$item['content'] = <<<EOD
<strong>Rank</strong><br>
<p>{$number}</p>
<Strong>Topic</strong><br>
<p>{$name}</p>
<Strong>Tweets</strong><br>
<p>{$tweets}</p>
EOD;
$item['timestamp'] = $updated;
$this->items[] = $item;
if (count($this->items) >= $limit) {
break;
}
}
}
public function getURI() {
if (!is_null($this->getInput('country'))) {
return self::URI . '/' . $this->getInput('country');
}
return parent::getURI();
}
public function getName() {
if (!is_null($this->getInput('country'))) {
$parameters = $this->getParameters();
$values = array_flip($parameters[0]['country']['values']);
return $values[$this->getInput('country')] . ' - TwitScoop';
}
return parent::getName();
}
}

View File

@@ -20,9 +20,7 @@ class TwitchBridge extends BridgeAbstract {
'All' => 'all',
'Archive' => 'archive',
'Highlights' => 'highlight',
'Uploads' => 'upload',
'Past Premieres' => 'past_premiere',
'Premiere Uploads' => 'premiere_upload'
'Uploads' => 'upload'
),
'defaultValue' => 'archive'
)
@@ -34,90 +32,43 @@ class TwitchBridge extends BridgeAbstract {
*/
const CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
const API_ENDPOINT = 'https://gql.twitch.tv/gql';
const BROADCAST_TYPES = array(
'all' => array(
'ARCHIVE',
'HIGHLIGHT',
'UPLOAD',
'PAST_PREMIERE',
'PREMIERE_UPLOAD'
),
'archive' => 'ARCHIVE',
'highlight' => 'HIGHLIGHT',
'upload' => 'UPLOAD',
'past_premiere' => 'PAST_PREMIERE',
'premiere_upload' => 'PREMIERE_UPLOAD'
);
public function collectData(){
$query = <<<'EOD'
query VODList($channel: String!, $types: [BroadcastType!]) {
user(login: $channel) {
displayName
videos(types: $types, sort: TIME) {
edges {
node {
id
title
publishedAt
lengthSeconds
viewCount
thumbnailURLs(width: 640, height: 360)
previewThumbnailURL(width: 640, height: 360)
description
tags
contentTags {
isLanguageTag
localizedName
}
game {
displayName
}
moments(momentRequestType: VIDEO_CHAPTER_MARKERS) {
edges {
node {
description
positionMilliseconds
}
}
}
}
}
}
}
}
EOD;
$variables = array(
'channel' => $this->getInput('channel'),
'types' => self::BROADCAST_TYPES[$this->getInput('type')]
// get channel user
$query_data = array(
'login' => $this->getInput('channel')
);
$data = $this->apiRequest($query, $variables);
$users = $this->apiGet('users', $query_data)->users;
if(count($users) === 0)
returnClientError('User "'
. $this->getInput('channel')
. '" could not be found');
$user = $users[0];
$user = $data->user;
foreach($user->videos->edges as $edge) {
$video = $edge->node;
$url = 'https://www.twitch.tv/videos/' . $video->id;
// get video list
$query_endpoint = 'channels/' . $user->_id . '/videos';
$query_data = array(
'broadcast_type' => $this->getInput('type'),
'limit' => 10
);
$videos = $this->apiGet($query_endpoint, $query_data)->videos;
foreach($videos as $video) {
$item = array(
'uri' => $url,
'uri' => $video->url,
'title' => $video->title,
'timestamp' => $video->publishedAt,
'author' => $user->displayName,
'timestamp' => $video->published_at,
'author' => $video->channel->display_name,
);
// Add categories for tags and played game
$item['categories'] = $video->tags;
if(!is_null($video->game))
$item['categories'][] = $video->game->displayName;
foreach($video->contentTags as $tag)
if(!$tag->isLanguageTag)
$item['categories'][] = $tag->localizedName;
$item['categories'] = array_filter(explode(' ', $video->tag_list));
if(!empty($video->game))
$item['categories'][] = $video->game;
// Add enclosures for thumbnails from a few points in the video
// Thumbnail list has duplicate entries sometimes so remove those
$item['enclosures'] = array_unique($video->thumbnailURLs);
$item['enclosures'] = array();
foreach($video->thumbnails->large as $thumbnail)
$item['enclosures'][] = $thumbnail->url;
/*
* Content format example:
@@ -135,45 +86,44 @@ EOD;
*
*/
$item['content'] = '<p><a href="'
. $url
. $video->url
. '"><img src="'
. $video->previewThumbnailURL
. $video->preview->large
. '" /></a></p><p>'
. $video->description // in markdown format
. $video->description_html
. '</p><p><b>Duration:</b> '
. $this->formatTimestampTime($video->lengthSeconds)
. $this->formatTimestampTime($video->length)
. '<br/><b>Views:</b> '
. $video->viewCount
. $video->views
. '</p>';
// Add played games list to content
$item['content'] .= '<p><b>Played games:</b><ul>';
if(count($video->moments->edges) > 0) {
foreach($video->moments->edges as $edge) {
$moment = $edge->node;
$item['categories'][] = $moment->description;
$video_id = trim($video->_id, 'v'); // _id gives 'v1234' but API wants '1234'
$markers = $this->apiGet('videos/' . $video_id . '/markers')->markers;
$item['content'] .= '<p><b>Played games:</b></b><ul><li><a href="'
. $video->url
. '">00:00:00</a> - '
. $video->game
. '</li>';
if(isset($markers->game_changes)) {
usort($markers->game_changes, function($a, $b) {
return $a->time - $b->time;
});
foreach($markers->game_changes as $game_change) {
$item['categories'][] = $game_change->label;
$item['content'] .= '<li><a href="'
. $url
. $video->url
. '?t='
. $this->formatQueryTime($moment->positionMilliseconds / 1000)
. $this->formatQueryTime($game_change->time)
. '">'
. $this->formatTimestampTime($moment->positionMilliseconds / 1000)
. $this->formatTimestampTime($game_change->time)
. '</a> - '
. $moment->description
. $game_change->label
. '</li>';
}
} else {
$item['content'] .= '<li><a href="'
. $url
. '">00:00:00</a> - '
. ($video->game ? $video->game->displayName : 'No Game')
. '</li>';
}
$item['content'] .= '</ul></p>';
$item['categories'] = array_unique($item['categories']);
$this->items[] = $item;
}
}
@@ -194,37 +144,25 @@ EOD;
$seconds % 60);
}
// GraphQL: https://graphql.org/
// Tool for developing/testing queries: https://github.com/skevy/graphiql-app
private function apiRequest($query, $variables) {
$request = array(
'query' => $query,
'variables' => $variables
);
/*
* Ideally the new 'helix' API should be used as v5/'kraken' is deprecated.
* The new API however still misses many features (markers, played game..) of
* the old one, so let's use the old one for as long as it's available.
*/
private function apiGet($endpoint, $query_data = array()) {
$query_data['api_version'] = 5;
$url = 'https://api.twitch.tv/kraken/'
. $endpoint
. '?'
. http_build_query($query_data);
$header = array(
'Client-ID: ' . self::CLIENT_ID
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($request)
);
Debug::log("Sending GraphQL query:\n" . $query);
Debug::log("Sending GraphQL variables:\n"
. json_encode($variables, JSON_PRETTY_PRINT));
$data = json_decode(getContents($url, $header))
or returnServerError('API request to "' . $url . '" failed.');
$response = json_decode(getContents(self::API_ENDPOINT, $header, $opts))
or returnServerError('API request to "' . self::API_ENDPOINT . '" failed.');
Debug::log("Got GraphQL response:\n"
. json_encode($response, JSON_PRETTY_PRINT));
if(isset($response->errors)) {
$messages = array_column($response->errors, 'message');
returnServerError('API error(s): ' . implode("\n", $messages));
}
return $response->data;
return $data;
}
public function getName(){

View File

@@ -75,12 +75,6 @@ EOD
'required' => false,
'type' => 'checkbox',
'title' => 'Hide retweets'
),
'nopinned' => array(
'name' => 'Without pinned tweet',
'required' => false,
'type' => 'checkbox',
'title' => 'Hide pinned tweet'
)
),
'By list' => array(
@@ -101,20 +95,6 @@ EOD
'required' => false,
'title' => 'Specify term to search for'
)
),
'By list ID' => array(
'listid' => array(
'name' => 'List ID',
'exampleValue' => '31748',
'required' => true,
'title' => 'Insert the list id'
),
'filter' => array(
'name' => 'Filter',
'exampleValue' => '#rss-bridge',
'required' => false,
'title' => 'Specify term to search for'
)
)
);
@@ -165,8 +145,6 @@ EOD
break;
case 'By list':
return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user');
case 'By list ID':
return 'Twitter List #' . $this->getInput('listid');
default: return parent::getName();
}
return 'Twitter ' . $specific . $this->getInput($param);
@@ -189,10 +167,6 @@ EOD
. urlencode($this->getInput('user'))
. '/lists/'
. str_replace(' ', '-', strtolower($this->getInput('list')));
case 'By list ID':
return self::URI
. 'i/lists/'
. urlencode($this->getInput('listid'));
default: return parent::getURI();
}
}
@@ -205,32 +179,15 @@ EOD
. urlencode($this->getInput('q'))
. '&tweet_mode=extended&tweet_search_mode=live';
case 'By username':
// use search endpoint if without replies or without retweets enabled
if ($this->getInput('noretweet') || $this->getInput('norep')) {
$query = 'from:' . $this->getInput('u');
// Twitter's from: search excludes retweets by default
if (!$this->getInput('noretweet')) $query .= ' include:nativeretweets';
if ($this->getInput('norep')) $query .= ' exclude:replies';
return self::API_URI
. '/2/search/adaptive.json?q='
. urlencode($query)
. '&tweet_mode=extended&tweet_search_mode=live';
} else {
return self::API_URI
. '/2/timeline/profile/'
. $this->getRestId($this->getInput('u'))
. '.json?tweet_mode=extended';
}
return self::API_URI
. '/2/timeline/profile/'
. $this->getRestId($this->getInput('u'))
. '.json?tweet_mode=extended';
case 'By list':
return self::API_URI
. '/2/timeline/list.json?list_id='
. $this->getListId($this->getInput('user'), $this->getInput('list'))
. '&tweet_mode=extended';
case 'By list ID':
return self::API_URI
. '/2/timeline/list.json?list_id='
. $this->getInput('listid')
. '&tweet_mode=extended';
default: returnServerError('Invalid query context !');
}
}
@@ -264,43 +221,7 @@ EOD
return $carry;
}, array());
$hidePinned = $this->getInput('nopinned');
if ($hidePinned) {
$pinnedTweetId = null;
if (isset($data->timeline->instructions[1]) && isset($data->timeline->instructions[1]->pinEntry)) {
$pinnedTweetId = $data->timeline->instructions[1]->pinEntry->entry->content->item->content->tweet->id;
}
}
$tweets = array();
// Extract tweets from timeline property when in username mode
// This fixes number of issues:
// * If there's a retweet of a quote tweet, the quoted tweet will not appear in results (since it wasn't retweeted directly)
// * Pinned tweets do not get stuck at the bottom
if ($this->queriedContext === 'By username') {
foreach($data->timeline->instructions[0]->addEntries->entries as $tweet) {
if (!isset($tweet->content->item)) continue;
$tweetId = $tweet->content->item->content->tweet->id;
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
if (!$selectedTweet) continue;
// If this is a retweet, it will contain shorter text and will point to the original full tweet (retweeted_status_id_str).
// Let's use the original tweet text.
if (isset($selectedTweet->retweeted_status_id_str)) {
$tweetId = $selectedTweet->retweeted_status_id_str;
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
if (!$selectedTweet) continue;
}
// use $tweetId as key to avoid duplicates (e.g. user retweeting their own tweet)
$tweets[$tweetId] = $selectedTweet;
}
} else {
foreach($data->globalObjects->tweets as $tweet) {
$tweets[] = $tweet;
}
}
foreach($tweets as $tweet) {
foreach($data->globalObjects->tweets as $tweet) {
/* Debug::log('>>> ' . json_encode($tweet)); */
// Skip spurious retweets
@@ -313,11 +234,6 @@ EOD
continue;
}
// Skip pinned tweet
if ($hidePinned && $tweet->id_str === $pinnedTweetId) {
continue;
}
$item = array();
// extract username and sanitize
$user_info = $this->getUserInformation($tweet->user_id_str, $data->globalObjects);
@@ -325,7 +241,7 @@ EOD
$item['username'] = $user_info->screen_name;
$item['fullname'] = $user_info->name;
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
if (null !== $this->getInput('u') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
if (null !== $this->getInput('u') && $item['username'] != $this->getInput('u')) {
$item['author'] .= ' RT: @' . $this->getInput('u');
}
$item['avatar'] = $user_info->profile_image_url_https;
@@ -438,7 +354,6 @@ EOD;
switch($this->queriedContext) {
case 'By list':
case 'By list ID':
// Check if filter applies to list (using raw content)
if($this->getInput('filter')) {
if(stripos($cleanedTweet, $this->getInput('filter')) === false) {
@@ -447,7 +362,7 @@ EOD;
}
break;
case 'By username':
if ($this->getInput('noretweet') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
if ($this->getInput('noretweet') && $item['username'] != $this->getInput('u')) {
continue 2; // switch + for-loop!
}
break;
@@ -607,12 +522,4 @@ EOD;
}
}
}
private function getTweet($tweetId, $apiData) {
if (property_exists($apiData->tweets, $tweetId)) {
return $apiData->tweets->$tweetId;
} else {
return null;
}
}
}

View File

@@ -8,7 +8,7 @@ class VarietyBridge extends FeedExpander {
const DESCRIPTION = 'RSS feed for Variety';
public function collectData(){
$this->collectExpandableDatas('https://feeds.feedburner.com/variety/headlines', 15);
$this->collectExpandableDatas('http://feeds.feedburner.com/variety/headlines', 15);
}
protected function parseItem($newsItem){

View File

@@ -232,16 +232,11 @@ class VkBridge extends BridgeAbstract
$div->outertext = '';
}
// get sign / post author
// get sign
$post_author = $pageName;
$author_selectors = array('a.wall_signed_by', 'a.author');
foreach($author_selectors as $author_selector) {
$a = $post->find($author_selector, 0);
if (is_object($a)) {
$post_author = $a->innertext;
$a->outertext = '';
break;
}
foreach($post->find('a.wall_signed_by') as $a) {
$post_author = $a->innertext;
$a->outertext = '';
}
// fix links and get post hashtags
@@ -279,24 +274,16 @@ class VkBridge extends BridgeAbstract
}
}
$copy_quote = $post->find('div.copy_quote', 0);
if (is_object($copy_quote)) {
if (is_object($post->find('div.copy_quote', 0))) {
if ($this->getInput('hide_reposts') === true) {
continue;
}
$copy_quote = $post->find('div.copy_quote', 0);
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
$copy_post_header->outertext = '';
}
$second_copy_quote = $copy_quote->find('div.published_sec_quote', 0);
if (is_object($second_copy_quote)) {
$second_copy_quote_author = $second_copy_quote->find('a.copy_author', 0)->outertext;
$second_copy_quote_content = $second_copy_quote->find('div.copy_post_date', 0)->outertext;
$second_copy_quote->outertext = "<br>Reposted ($second_copy_quote_author): $second_copy_quote_content";
}
$copy_quote_author = $copy_quote->find('a.copy_author', 0)->outertext;
$copy_quote_content = $copy_quote->innertext;
$copy_quote->outertext = "<br>Reposted ($copy_quote_author): <br>$copy_quote_content";
$copy_quote->outertext = "<br>Reposted: <br>$copy_quote_content";
}
$item = array();
@@ -346,7 +333,7 @@ class VkBridge extends BridgeAbstract
$data = json_decode($arg, true);
if ($data == null) return;
$thumb = $data['temp']['base'] . $data['temp']['x_'][0];
$thumb = $data['temp']['base'] . $data['temp']['x_'][0] . '.jpg';
$original = '';
foreach(array('y_', 'z_', 'w_') as $key) {
if (!isset($data['temp'][$key])) continue;
@@ -356,7 +343,7 @@ class VkBridge extends BridgeAbstract
} else {
$base = $data['temp']['base'];
}
$original = $base . $data['temp'][$key][0];
$original = $base . $data['temp'][$key][0] . '.jpg';
}
if ($original) {
@@ -379,7 +366,6 @@ class VkBridge extends BridgeAbstract
return $time;
} else {
$strdate = $post->find('span.rel_date', 0)->plaintext;
$strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate);
$date = date_parse($strdate);
if (!$date['year']) {

View File

@@ -1,50 +0,0 @@
<?php
class WallmineNewsBridge extends BridgeAbstract {
const NAME = 'Wallmine News Bridge';
const URI = 'https://wallmine.com';
const DESCRIPTION = 'Returns financial news';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
const CACHE_TIMEOUT = 900; // 15 mins
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/news/')
or returnServerError('Could not request: ' . $this->getURI() . '/news/');
$html = defaultLinkTo($html, self::URI);
foreach($html->find('div.container.news-card') as $div) {
$item = array();
$item['uri'] = $div->find('a', 0)->href;
$image = $div->find('img.img-fluid', 0)->src;
$page = getSimpleHTMLDOMCached($item['uri'], 7200)
or returnServerError('Could not request: ' . $item['uri']);
$article = $page->find('div.container.article-container', 0);
$item['title'] = $article->find('h1', 0)->plaintext;
$article->find('p.published-on', 0)->children(0)->outertext = '';
$article->find('p.published-on', 0)->children(1)->outertext = '';
$date = str_replace('at', '', $article->find('p.published-on', 0)->innertext);
$item['timestamp'] = $date;
$article->find('h1', 0)->outertext = '';
$article->find('p.published-on', 0)->outertext = '';
$item['content'] = $article->innertext;
$item['enclosures'][] = $image;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
}

View File

@@ -92,9 +92,9 @@ class WordPressBridge extends FeedExpander {
returnClientError('The url parameter must either refer to http or https protocol.');
}
try{
$this->collectExpandableDatas($this->getURI() . '/feed/atom/', 20);
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
} catch (Exception $e) {
$this->collectExpandableDatas($this->getURI() . '/?feed=atom', 20);
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
}
}

View File

@@ -2,7 +2,7 @@
class WorldCosplayBridge extends BridgeAbstract {
const NAME = 'WorldCosplay Bridge';
const URI = 'https://worldcosplay.net/';
const DESCRIPTION = 'Returns WorldCosplay photos';
const DESCRIPTION ='Returns WorldCosplay photos';
const MAINTAINER = 'AxorPL';
const API_CHARACTER = 'api/photo/list.json?character_id=%u&limit=%u';
@@ -95,12 +95,14 @@ class WorldCosplayBridge extends BridgeAbstract {
$json = json_decode(getContents($url))
or returnServerError(sprintf(self::ERR_QUERY, $url));
if($json->has_error) {
if($json->has_error)
{
returnServerError($json->message);
}
$list = $json->list;
foreach($list as $img) {
foreach($list as $img)
{
$item = array();
$item['uri'] = self::URI . substr($img->photo->url, 1);
$item['title'] = $img->photo->subject;

View File

@@ -1,251 +0,0 @@
<?php
class XPathBridge extends XPathAbstract {
const NAME = 'XPathBridge';
const URI = 'https://github.com/rss-bridge/rss-bridge';
const DESCRIPTION
= 'Parse any webpage using <a href="https://devhints.io/xpath" target="_blank">XPath expressions</a>';
const MAINTAINER = 'Niehztog';
const PARAMETERS = array(
'' => array(
'url' => array(
'name' => 'Enter web page URL',
'title' => <<<"EOL"
You can specify any website URL which serves data suited for display in RSS feeds
(for example a news blog).
EOL
, 'type' => 'text',
'exampleValue' => 'https://news.blizzard.com/en-en',
'defaultValue' => 'https://news.blizzard.com/en-en',
'required' => true
),
'item' => array(
'name' => 'Item selector',
'title' => <<<"EOL"
Enter an XPath expression matching a list of dom nodes, each node containing one
feed article item in total (usually a surrounding &lt;div&gt; or &lt;span&gt; tag). This will
be the context nodes for all of the following expressions. This expression usually
starts with a single forward slash.
EOL
, 'type' => 'text',
'exampleValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article',
'defaultValue' => '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article',
'required' => true
),
'title' => array(
'name' => 'Item title selector',
'title' => <<<"EOL"
This expression should match a node contained within each article item node
containing the article headline. It should start with a dot followed by two
forward slashes, referring to any descendant nodes of the article item node.
EOL
, 'type' => 'text',
'exampleValue' => './/div/div[2]/h2',
'defaultValue' => './/div/div[2]/h2',
'required' => true
),
'content' => array(
'name' => 'Item description selector',
'title' => <<<"EOL"
This expression should match a node contained within each article item node
containing the article content or description. It should start with a dot
followed by two forward slashes, referring to any descendant nodes of the
article item node.
EOL
, 'type' => 'text',
'exampleValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]',
'defaultValue' => './/div[@class="ArticleListItem-description"]/div[@class="h6"]',
'required' => false
),
'uri' => array(
'name' => 'Item URL selector',
'title' => <<<"EOL"
This expression should match a node's attribute containing the article URL
(usually the href attribute of an &lt;a&gt; tag). It should start with a dot
followed by two forward slashes, referring to any descendant nodes of
the article item node. Attributes can be selected by prepending an @ char
before the attributes name.
EOL
, 'type' => 'text',
'exampleValue' => './/a[@class="ArticleLink ArticleLink"]/@href',
'defaultValue' => './/a[@class="ArticleLink ArticleLink"]/@href',
'required' => false
),
'author' => array(
'name' => 'Item author selector',
'title' => <<<"EOL"
This expression should match a node contained within each article item
node containing the article author's name. It should start with a dot
followed by two forward slashes, referring to any descendant nodes of
the article item node.
EOL
, 'type' => 'text',
'required' => false
),
'timestamp' => array(
'name' => 'Item date selector',
'title' => <<<"EOL"
This expression should match a node or node's attribute containing the
article timestamp or date (parsable by PHP's strtotime function). It
should start with a dot followed by two forward slashes, referring to
any descendant nodes of the article item node. Attributes can be
selected by prepending an @ char before the attributes name.
EOL
, 'type' => 'text',
'exampleValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp',
'defaultValue' => './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp',
'required' => false
),
'enclosures' => array(
'name' => 'Item image selector',
'title' => <<<"EOL"
This expression should match a node's attribute containing an article
image URL (usually the src attribute of an &lt;img&gt; tag or a style
attribute). It should start with a dot followed by two forward slashes,
referring to any descendant nodes of the article item node. Attributes
can be selected by prepending an @ char before the attributes name.
EOL
, 'type' => 'text',
'exampleValue' => './/div[@class="ArticleListItem-image"]/@style',
'defaultValue' => './/div[@class="ArticleListItem-image"]/@style',
'required' => false
),
'categories' => array(
'name' => 'Item category selector',
'title' => <<<"EOL"
This expression should match a node or node's attribute contained
within each article item node containing the article category. This
could be inside &lt;div&gt; or &lt;span&gt; tags or sometimes be hidden
in a data attribute. It should start with a dot followed by two
forward slashes, referring to any descendant nodes of the article
item node. Attributes can be selected by prepending an @ char
before the attributes name.
EOL
, 'type' => 'text',
'exampleValue' => './/div[@class="ArticleListItem-label"]',
'defaultValue' => './/div[@class="ArticleListItem-label"]',
'required' => false
),
'fix_encoding' => array(
'name' => 'Fix encoding',
'title' => <<<"EOL"
Check this to fix feed encoding by invoking PHP's utf8_decode
function on all extracted texts. Try this in case you see "broken" or
"weird" characters in your feed where you'd normally expect umlauts
or any other non-ascii characters.
EOL
, 'type' => 'checkbox',
'required' => false
),
)
);
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl(){
return $this->encodeUri($this->getInput('url'));
}
/**
* XPath expression for extracting the feed items from the source page
* @return string
*/
protected function getExpressionItem(){
return urldecode($this->getInput('item'));
}
/**
* XPath expression for extracting an item title from the item context
* @return string
*/
protected function getExpressionItemTitle(){
return urldecode($this->getInput('title'));
}
/**
* XPath expression for extracting an item's content from the item context
* @return string
*/
protected function getExpressionItemContent(){
return urldecode($this->getInput('content'));
}
/**
* XPath expression for extracting an item link from the item context
* @return string
*/
protected function getExpressionItemUri(){
return urldecode($this->getInput('uri'));
}
/**
* XPath expression for extracting an item author from the item context
* @return string
*/
protected function getExpressionItemAuthor(){
return urldecode($this->getInput('author'));
}
/**
* XPath expression for extracting an item timestamp from the item context
* @return string
*/
protected function getExpressionItemTimestamp(){
return urldecode($this->getInput('timestamp'));
}
/**
* XPath expression for extracting item enclosures (media content like
* images or movies) from the item context
* @return string
*/
protected function getExpressionItemEnclosures(){
return urldecode($this->getInput('enclosures'));
}
/**
* XPath expression for extracting an item category from the item context
* @return string
*/
protected function getExpressionItemCategories(){
return urldecode($this->getInput('categories'));
}
/**
* Fix encoding
* @return string
*/
protected function getSettingFixEncoding(){
return $this->getInput('fix_encoding');
}
/**
* Fixes URL encoding issues in input URL's
* @param $uri
* @return string|string[]
*/
private function encodeUri($uri)
{
if (strpos($uri, 'https%3A%2F%2F') === 0
|| strpos($uri, 'http%3A%2F%2F') === 0) {
$uri = urldecode($uri);
}
$uri = str_replace('|', '%7C', $uri);
return $uri;
}
}

View File

@@ -1,93 +0,0 @@
<?php
class YeggiBridge extends BridgeAbstract {
const NAME = 'Yeggi Search';
const URI = 'https://www.yeggi.com';
const DESCRIPTION = 'Returns feeds for search results';
const MAINTAINER = 'AntoineTurmel';
const PARAMETERS = array(
array(
'query' => array(
'name' => 'Search query',
'type' => 'text',
'required' => true,
'title' => 'Insert your search term here',
'exampleValue' => 'Enter your search term'
),
'sortby' => array(
'name' => 'Sort by',
'type' => 'list',
'required' => false,
'values' => array(
'Best match' => '0',
'Popular' => '1',
'Latest' => '2',
),
'defaultValue' => 'newest'
),
'show' => array(
'name' => 'Show',
'type' => 'list',
'required' => false,
'values' => array(
'All' => '0',
'Free' => '1',
'For sale' => '2',
),
'defaultValue' => 'all'
),
'showimage' => array(
'name' => 'Show image in content',
'type' => 'checkbox',
'required' => false,
'title' => 'Activate to show the image in the content',
'defaultValue' => 'checked'
)
)
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed to receive ' . $this->getURI());
$results = $html->find('div.item_1_A');
foreach($results as $result) {
$item = array();
$title = $result->find('.item_3_B_2', 0)->plaintext;
$explodeTitle = explode('&nbsp; ', $title);
if(count($explodeTitle) == 2) {
$item['title'] = $explodeTitle[1];
} else {
$item['title'] = $explodeTitle[0];
}
$item['uri'] = self::URI . $result->find('a', 0)->href;
$item['author'] = 'Yeggi';
$item['content'] = '';
$item['uid'] = hash('md5', $item['title']);
$image = $result->find('img', 0)->src;
if($this->getInput('showimage')) {
$item['content'] .= '<img src="' . $image . '">';
}
$item['enclosures'] = array($image);
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('query'))) {
$uri = self::URI . '/q/' . urlencode($this->getInput('query')) . '/';
$uri .= '?o_f=' . $this->getInput('show');
$uri .= '&o_s=' . $this->getInput('sortby');
return $uri;
}
return parent::getURI();
}
}

View File

@@ -8,7 +8,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
*/
const NAME = 'Zone Telechargement';
const URI = 'https://www.zt-za.net/';
const URI = 'https://www.zt-za.com/';
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
@@ -18,45 +18,21 @@ class ZoneTelechargementBridge extends BridgeAbstract {
'type' => 'text',
'required' => true,
'title' => 'URL d\'une série sans le https://www.zt-za.com/',
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'),
'filter' => array(
'name' => 'Type de contenu',
'type' => 'list',
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
'values' => array(
'Streaming et Téléchargement' => 'both',
'Téléchargement' => 'download',
'Streaming' => 'streaming'
),
'defaultValue' => 'both'
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
)
)
);
// This is an URL that is not protected by robot protection for Direct Download
const UNPROTECTED_URI = 'https://www.zone-telechargement.net/';
// This is an URL that is not protected by robot protection
const UNPROTECED_URI = 'https://www.zone-annuaire.com/';
// This is an URL that is not protected by robot protection for Streaming Links
const UNPROTECTED_URI_STREAMING = 'https://zone-telechargement.stream/';
// This function use curl library with curl as User Agent instead of
// simple_html_dom to load the HTML content as the website has some captcha
// request for other user agents
private function loadURL($url){
$header = array();
$opts = array(CURLOPT_USERAGENT => 'curl/7.64.0');
$html = getContents($url, $header, $opts)
or returnServerError('Could not request Zone Telechargement.');
return str_get_html($html);
}
public function getIcon(){
return self::UNPROTECTED_URI . '/templates/Default/images/favicon.ico';
public function getIcon() {
return self::URI . '/templates/Default/images/favicon.ico';
}
public function collectData(){
$html = $this->loadURL(self::UNPROTECTED_URI . $this->getInput('url'));
$filter = $this->getInput('filter');
$html = getSimpleHTMLDOM(self::UNPROTECED_URI . $this->getInput('url'))
or returnServerError('Could not request Zone Telechargement.');
// Get the TV show title
$qualityselector = 'div[style=font-size: 18px;margin: 10px auto;color:red;font-weight:bold;text-align:center;]';
@@ -64,71 +40,35 @@ class ZoneTelechargementBridge extends BridgeAbstract {
$quality = trim(explode("\n", $html->find($qualityselector, 0)->plaintext)[0]);
$this->showTitle = $show . ' ' . $quality;
// Get the post content
$linkshtml = $html->find('div[class=postinfo]', 0);
$episodes = array();
// Handle the Direct Download links
if($filter == 'both' || $filter == 'download') {
// Get the post content
$linkshtml = $html->find('div[class=postinfo]', 0);
$list = $linkshtml->find('a');
// Construct the tabble of episodes using the links
foreach($list as $element) {
// Retrieve episode number from link text
$epnumber = explode(' ', $element->plaintext)[1];
$hoster = $this->findLinkHoster($element);
$list = $linkshtml->find('a');
// Construct the table of episodes using the links
foreach($list as $element) {
// Retrieve episode number from link text
$epnumber = explode(' ', $element->plaintext)[1];
$hoster = $this->findLinkHoster($element);
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber][] = '<a href="' . $element->href . '">' . $hoster . ' - '
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber]['ddl'][] = '<a href="' . $element->href . '">' . $hoster . ' - '
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
}
}
// Handle the Streaming links
if($filter == 'both' || $filter == 'streaming') {
// Get the post content, on the dedicated streaming website
$htmlstreaming = $this->loadURL(self::UNPROTECTED_URI_STREAMING . $this->getInput('url'));
// Get the HTML element containing all the links
$streaminglinkshtml = $htmlstreaming->find('p[style=background-color: #FECC00;]', 1)->parent()->next_sibling();
// Get all streaming Links
$liststreaming = $streaminglinkshtml->find('a');
foreach($liststreaming as $elementstreaming) {
// Retrieve the episode number from the link text
$epnumber = explode(' ', $elementstreaming->plaintext)[1];
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber]['streaming'][] = '<a href="' . $elementstreaming->href . '">'
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
}
}
// Finally construct the items array
foreach($episodes as $epnum => $episode) {
// Handle the Direct Download links
if(array_key_exists('ddl', $episode)) {
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode['ddl']);
$item['title'] = $this->showTitle . ' Episode ' . $epnum . ' - Téléchargement';
// Generate an unique UID by hashing the item title to prevent confusion for RSS readers
$item['uid'] = hash('md5', $item['title']);
$item['uri'] = self::URI . $this->getInput('url');
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
}
// Handle the streaming link
if(array_key_exists('streaming', $episode)) {
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode['streaming']);
$item['title'] = $this->showTitle . ' Episode ' . $epnum . ' - Streaming';
// Generate an unique UID by hashing the item title to prevent confusion for RSS readers
$item['uid'] = hash('md5', $item['title']);
$item['uri'] = self::URI . $this->getInput('url');
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
}
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode);
$item['title'] = $this->showTitle . ' Episode ' . $epnum;
// As RSS Bridge use the URI as GUID they need to be unique : adding a md5 hash of the title element
// should geneerate unique URI to prevent confusion for RSS readers
$item['uri'] = self::URI . $this->getInput('url') . '#' . hash('md5', $item['title']);
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
}
}
@@ -142,19 +82,11 @@ class ZoneTelechargementBridge extends BridgeAbstract {
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkHoster($element) {
// The hoster name is one level higher than the link tag : get the parent element
$element = $element->parent();
//echo "PARENT : $element \n";
$continue = true;
// Walk through all elements in the reverse order until finding the one with a div and that is not a <br/>
while(!($element->find('div', 0) != null && $element->tag != 'br')) {
$element = $element->prev_sibling();

View File

@@ -34,7 +34,6 @@
},
"suggest": {
"ext-memcached": "Allows to use memcached as cache type",
"ext-sqlite3": "Allows to use an SQLite database for caching",
"ext-dom": "Allows to use some bridges based on XPath expressions"
"ext-sqlite3": "Allows to use an SQLite database for caching"
}
}

View File

@@ -19,7 +19,7 @@ class HtmlFormat extends FormatAbstract {
continue;
}
$query = str_ireplace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING']));
$query = str_replace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING']));
$buttons .= $this->buildButton($format, $query) . PHP_EOL;
$mime = $formatFac->create($format)->getMimeType();

View File

@@ -61,13 +61,6 @@ abstract class BridgeAbstract implements BridgeInterface {
*/
const CACHE_TIMEOUT = 3600;
/**
* Configuration for the bridge
*
* Use {@see BridgeAbstract::getConfiguration()} to read this parameter
*/
const CONFIGURATION = array();
/**
* Parameters for the bridge
*
@@ -245,36 +238,6 @@ abstract class BridgeAbstract implements BridgeInterface {
}
/**
* Loads configuration for the bridge
*
* Returns errors and aborts execution if the provided configuration is
* invalid.
*
* @return void
*/
public function loadConfiguration() {
foreach(static::CONFIGURATION as $optionName => $optionValue) {
$configurationOption = Configuration::getConfig(get_class($this), $optionName);
if($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
if(isset($optionValue['required']) && $optionValue['required'] === true) {
returnServerError(
'Missing configuration option: '
. $optionName
);
} elseif(isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
}
}
/**
* Returns the value for the provided input
*
@@ -288,19 +251,6 @@ abstract class BridgeAbstract implements BridgeInterface {
return $this->inputs[$this->queriedContext][$input]['value'];
}
/**
* Returns the value for the selected configuration
*
* @param string $input The option name
* @return mixed|null The option value or null if the input is not defined
*/
public function getOption($name){
if(!isset($this->configuration[$name])) {
return null;
}
return $this->configuration[$name];
}
/** {@inheritdoc} */
public function getDescription(){
return static::DESCRIPTION;
@@ -318,12 +268,7 @@ abstract class BridgeAbstract implements BridgeInterface {
/** {@inheritdoc} */
public function getIcon(){
return static::URI . '/favicon.ico';
}
/** {@inheritdoc} */
public function getConfiguration(){
return static::CONFIGURATION;
return '';
}
/** {@inheritdoc} */

View File

@@ -126,7 +126,7 @@ This bridge is not fetching its content through a secure connection</div>';
if(isset($inputEntry['title']))
$form .= '<i class="info" title="' . filter_var($inputEntry['title'], FILTER_SANITIZE_STRING) . '">i</i>';
else
$form .= '<i class="no-info"></i>';
$form .= '<i></i>';
}
$form .= '</div>';
@@ -347,12 +347,10 @@ This bridge is not fetching its content through a secure connection</div>';
CARD;
// If we don't have any parameter for the bridge, we print a generic form to load it.
if (count($parameters) === 0) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
if(count($parameters) === 0
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
} else if (count($parameters) === 1 && array_key_exists('global', $parameters)) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
} else {

View File

@@ -58,19 +58,6 @@ interface BridgeInterface {
*/
public function collectData();
/**
* Get the user's supplied configuration for the bridge
*/
public function getConfiguration();
/**
* Returns the value for the selected configuration
*
* @param string $input The option name
* @return mixed|null The option value or null if the input is not defined
*/
public function getOption($name);
/**
* Returns the description
*

View File

@@ -129,7 +129,7 @@ EOD;
* @return string The searchbar
*/
private static function getSearchbar() {
$query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS);
$query = filter_input(INPUT_GET, 'q');
return <<<EOD
<section class="searchbar">

View File

@@ -28,7 +28,7 @@ final class Configuration {
*
* @todo Replace this property by a constant.
*/
public static $VERSION = 'dev.2021-04-25';
public static $VERSION = 'dev.2020-02-26';
/**
* Holds the configuration data.
@@ -145,7 +145,10 @@ final class Configuration {
// Replace default configuration with custom settings
foreach(parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) {
foreach($section as $key => $value) {
Configuration::$config[$header][$key] = $value;
// Skip unknown sections and keys
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
Configuration::$config[$header][$key] = $value;
}
}
}
}
@@ -215,11 +218,13 @@ final class Configuration {
* @return mixed|null The parameter value.
*/
public static function getConfig($section, $key) {
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
return self::$config[$section][$key];
}
return null;
}
/**

View File

@@ -270,9 +270,7 @@ abstract class FeedExpander extends BridgeAbstract {
foreach($feedItem->link as $link) {
if(strtolower($link['rel']) === 'alternate') {
$item['uri'] = (string)$link['href'];
}
if(strtolower($link['rel']) === 'enclosure') {
$item['enclosures'][] = (string)$link['href'];
break;
}
}
}

View File

@@ -46,13 +46,7 @@ class FormatFactory extends FactoryAbstract {
throw new \InvalidArgumentException('Format name invalid!');
}
$name = $this->sanitizeFormatName($name);
if (is_null($name)) {
throw new \InvalidArgumentException('Unknown format given!');
}
$name .= 'Format';
$name = $this->sanitizeFormatName($name) . 'Format';
$pathFormat = $this->getWorkingDir() . $name . '.php';
if(!file_exists($pathFormat)) {
@@ -78,7 +72,7 @@ class FormatFactory extends FactoryAbstract {
* @return bool true if the name is a valid format name, false otherwise.
*/
public function isFormatName($name){
return is_string($name) && preg_match('/^[a-zA-Z0-9-]*$/', $name) === 1;
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
}
/**
@@ -114,6 +108,8 @@ class FormatFactory extends FactoryAbstract {
* * The PHP file name without file extension (i.e. `AtomFormat`)
* * The format name (i.e. `Atom`)
*
* Casing is ignored (i.e. `ATOM` and `atom` are the same).
*
* A format file matching the given format name must exist in the working
* directory!
*
@@ -122,7 +118,6 @@ class FormatFactory extends FactoryAbstract {
* valid, null otherwise.
*/
protected function sanitizeFormatName($name) {
$name = ucfirst(strtolower($name));
if(is_string($name)) {
@@ -136,12 +131,18 @@ class FormatFactory extends FactoryAbstract {
$name = $matches[1];
}
// The name is valid if a corresponding format file is found on disk
// Improve performance for correctly written format names
if(in_array($name, $this->getFormatNames())) {
$index = array_search($name, $this->getFormatNames());
return $this->getFormatNames()[$index];
}
// The name is valid if a corresponding format file is found on disk
if(in_array(strtolower($name), array_map('strtolower', $this->getFormatNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->getFormatNames()));
return $this->getFormatNames()[$index];
}
Debug::log('Invalid format name: "' . $name . '"!');
}

View File

@@ -1,583 +0,0 @@
<?php
/**
* An alternative abstract class for bridges utilizing XPath expressions
*
* This class is meant as an alternative base class for bridge implementations.
* It offers preliminary functionality for generating feeds based on XPath
* expressions.
* As a minimum, extending classes should define XPath expressions pointing
* to the feed items contents in the class constants below. In case there is
* more manual fine tuning required, it offers a bunch of methods which can
* be overridden, for example in order to specify formatting of field values
* or more flexible definition of dynamic XPath expressions.
*
* This class extends {@see BridgeAbstract}, which means it incorporates and
* extends all of its functionality.
**/
abstract class XPathAbstract extends BridgeAbstract {
/**
* Source Web page URL (should provide either HTML or XML content)
* You can specify any website URL which serves data suited for display in RSS feeds
* (for example a news blog).
*
* Use {@see XPathAbstract::getSourceUrl()} to read this parameter
*/
const FEED_SOURCE_URL = '';
/**
* XPath expression for extracting the feed title from the source page.
* If this is left blank or does not provide any data {@see BridgeAbstract::getName()}
* is used instead as the feed's title.
*
* Use {@see XPathAbstract::getExpressionTitle()} to read this parameter
*/
const XPATH_EXPRESSION_FEED_TITLE = './/title';
/**
* XPath expression for extracting the feed favicon URL from the source page.
* If this is left blank or does not provide any data {@see BridgeAbstract::getIcon()}
* is used instead as the feed's favicon URL.
*
* Use {@see XPathAbstract::getExpressionIcon()} to read this parameter
*/
const XPATH_EXPRESSION_FEED_ICON = './/link[@rel="icon"]/@href';
/**
* XPath expression for extracting the feed items from the source page
* Enter an XPath expression matching a list of dom nodes, each node containing one
* feed article item in total (usually a surrounding <div> or <span> tag). This will
* be the context nodes for all of the following expressions. This expression usually
* starts with a single forward slash.
*
* Use {@see XPathAbstract::getExpressionItem()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM = '';
/**
* XPath expression for extracting an item title from the item context
* This expression should match a node contained within each article item node
* containing the article headline. It should start with a dot followed by two
* forward slashes, referring to any descendant nodes of the article item node.
*
* Use {@see XPathAbstract::getExpressionItemTitle()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_TITLE = '';
/**
* XPath expression for extracting an item's content from the item context
* This expression should match a node contained within each article item node
* containing the article content or description. It should start with a dot
* followed by two forward slashes, referring to any descendant nodes of the
* article item node.
*
* Use {@see XPathAbstract::getExpressionItemContent()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_CONTENT = '';
/**
* XPath expression for extracting an item link from the item context
* This expression should match a node's attribute containing the article URL
* (usually the href attribute of an <a> tag). It should start with a dot
* followed by two forward slashes, referring to any descendant nodes of
* the article item node. Attributes can be selected by prepending an @ char
* before the attributes name.
*
* Use {@see XPathAbstract::getExpressionItemUri()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_URI = '';
/**
* XPath expression for extracting an item author from the item context
* This expression should match a node contained within each article item
* node containing the article author's name. It should start with a dot
* followed by two forward slashes, referring to any descendant nodes of
* the article item node.
*
* Use {@see XPathAbstract::getExpressionItemAuthor()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
/**
* XPath expression for extracting an item timestamp from the item context
* This expression should match a node or node's attribute containing the
* article timestamp or date (parsable by PHP's strtotime function). It
* should start with a dot followed by two forward slashes, referring to
* any descendant nodes of the article item node. Attributes can be
* selected by prepending an @ char before the attributes name.
*
* Use {@see XPathAbstract::getExpressionItemTimestamp()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_TIMESTAMP = '';
/**
* XPath expression for extracting item enclosures (media content like
* images or movies) from the item context
* This expression should match a node's attribute containing an article
* image URL (usually the src attribute of an <img> tag or a style
* attribute). It should start with a dot followed by two forward slashes,
* referring to any descendant nodes of the article item node. Attributes
* can be selected by prepending an @ char before the attributes name.
*
* Use {@see XPathAbstract::getExpressionItemEnclosures()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_ENCLOSURES = '';
/**
* XPath expression for extracting an item category from the item context
* This expression should match a node or node's attribute contained
* within each article item node containing the article category. This
* could be inside <div> or <span> tags or sometimes be hidden
* in a data attribute. It should start with a dot followed by two
* forward slashes, referring to any descendant nodes of the article
* item node. Attributes can be selected by prepending an @ char
* before the attributes name.
*
* Use {@see XPathAbstract::getExpressionItemCategories()} to read this parameter
*/
const XPATH_EXPRESSION_ITEM_CATEGORIES = '';
/**
* Fix encoding
* Set this to true for fixing feed encoding by invoking PHP's utf8_decode
* function on all extracted texts. Try this in case you see "broken" or
* "weird" characters in your feed where you'd normally expect umlauts
* or any other non-ascii characters.
*
* Use {@see XPathAbstract::getSettingFixEncoding()} to read this parameter
*/
const SETTING_FIX_ENCODING = false;
/**
* Internal storage for resulting feed name, automatically detected
* @var string
*/
private $feedName;
/**
* Internal storage for resulting feed name, automatically detected
* @var string
*/
private $feedUri;
/**
* Internal storage for resulting feed favicon, automatically detected
* @var string
*/
private $feedIcon;
public function getName(){
return $this->feedName ?: parent::getName();
}
public function getURI() {
return $this->feedUri ?: parent::getURI();
}
public function getIcon() {
return $this->feedIcon ?: parent::getIcon();
}
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl(){
return static::FEED_SOURCE_URL;
}
/**
* XPath expression for extracting the feed title from the source page
* @return string
*/
protected function getExpressionTitle(){
return static::XPATH_EXPRESSION_FEED_TITLE;
}
/**
* XPath expression for extracting the feed favicon from the source page
* @return string
*/
protected function getExpressionIcon(){
return static::XPATH_EXPRESSION_FEED_ICON;
}
/**
* XPath expression for extracting the feed items from the source page
* @return string
*/
protected function getExpressionItem(){
return static::XPATH_EXPRESSION_ITEM;
}
/**
* XPath expression for extracting an item title from the item context
* @return string
*/
protected function getExpressionItemTitle(){
return static::XPATH_EXPRESSION_ITEM_TITLE;
}
/**
* XPath expression for extracting an item's content from the item context
* @return string
*/
protected function getExpressionItemContent(){
return static::XPATH_EXPRESSION_ITEM_CONTENT;
}
/**
* XPath expression for extracting an item link from the item context
* @return string
*/
protected function getExpressionItemUri(){
return static::XPATH_EXPRESSION_ITEM_URI;
}
/**
* XPath expression for extracting an item author from the item context
* @return string
*/
protected function getExpressionItemAuthor(){
return static::XPATH_EXPRESSION_ITEM_AUTHOR;
}
/**
* XPath expression for extracting an item timestamp from the item context
* @return string
*/
protected function getExpressionItemTimestamp(){
return static::XPATH_EXPRESSION_ITEM_TIMESTAMP;
}
/**
* XPath expression for extracting item enclosures (media content like
* images or movies) from the item context
* @return string
*/
protected function getExpressionItemEnclosures(){
return static::XPATH_EXPRESSION_ITEM_ENCLOSURES;
}
/**
* XPath expression for extracting an item category from the item context
* @return string
*/
protected function getExpressionItemCategories(){
return static::XPATH_EXPRESSION_ITEM_CATEGORIES;
}
/**
* Fix encoding
* @return string
*/
protected function getSettingFixEncoding(){
return static::SETTING_FIX_ENCODING;
}
/**
* Internal helper method for quickly accessing all the user defined constants
* in derived classes
*
* @param $name
* @return bool|string
*/
private function getParam($name){
switch($name) {
case 'url':
return $this->getSourceUrl();
case 'feed_title':
return $this->getExpressionTitle();
case 'feed_icon':
return $this->getExpressionIcon();
case 'item':
return $this->getExpressionItem();
case 'title':
return $this->getExpressionItemTitle();
case 'content':
return $this->getExpressionItemContent();
case 'uri':
return $this->getExpressionItemUri();
case 'author':
return $this->getExpressionItemAuthor();
case 'timestamp':
return $this->getExpressionItemTimestamp();
case 'enclosures':
return $this->getExpressionItemEnclosures();
case 'categories':
return $this->getExpressionItemCategories();
case 'fix_encoding':
return $this->getSettingFixEncoding();
}
}
/**
* Should provide the source website HTML content
* can be easily overwritten for example if special headers or auth infos are required
* @return string
*/
protected function provideWebsiteContent() {
return getContents($this->feedUri);
}
/**
* Should provide the feeds title
*
* @param DOMXPath $xpath
* @return string
*/
protected function provideFeedTitle(DOMXPath $xpath) {
$title = $xpath->query($this->getParam('feed_title'));
if(count($title) === 1) {
return $this->getItemValueOrNodeValue($title);
}
}
/**
* Should provide the URL of the feed's favicon
*
* @param DOMXPath $xpath
* @return string
*/
protected function provideFeedIcon(DOMXPath $xpath) {
$icon = $xpath->query($this->getParam('feed_icon'));
if(count($icon) === 1) {
return $this->cleanImageUrl($this->getItemValueOrNodeValue($icon));
}
}
/**
* Should provide the feed's items.
*
* @param DOMXPath $xpath
* @return DOMNodeList
*/
protected function provideFeedItems(DOMXPath $xpath) {
return @$xpath->query($this->getParam('item'));
}
public function collectData() {
$this->feedUri = $this->getParam('url');
$webPageHtml = new DOMDocument();
libxml_use_internal_errors(true);
$webPageHtml->loadHTML($this->provideWebsiteContent());
libxml_clear_errors();
libxml_use_internal_errors(false);
$xpath = new DOMXPath($webPageHtml);
$this->feedName = $this->provideFeedTitle($xpath);
$this->feedIcon = $this->provideFeedIcon($xpath);
$entries = $this->provideFeedItems($xpath);
if($entries === false) {
return;
}
foreach ($entries as $entry) {
$item = new \FeedItem();
foreach(array('title', 'content', 'uri', 'author', 'timestamp', 'enclosures', 'categories') as $param) {
$expression = $this->getParam($param);
if('' === $expression) {
continue;
}
//can be a string or DOMNodeList, depending on the expression result
$typedResult = @$xpath->evaluate($expression, $entry);
if ($typedResult === false || ($typedResult instanceof DOMNodeList && count($typedResult) === 0)
|| (is_string($typedResult) && strlen(trim($typedResult)) === 0)) {
continue;
}
$item->__set($param, $this->formatParamValue($param, $this->getItemValueOrNodeValue($typedResult)));
}
$itemId = $this->generateItemId($item);
if(null !== $itemId) {
$item->setUid($itemId);
}
$this->items[] = $item;
}
}
/**
* @param $param
* @param $value
* @return string|array
*/
protected function formatParamValue($param, $value)
{
$value = $this->fixEncoding($value);
switch ($param) {
case 'title':
return $this->formatItemTitle($value);
case 'content':
return $this->formatItemContent($value);
case 'uri':
return $this->formatItemUri($value);
case 'author':
return $this->formatItemAuthor($value);
case 'timestamp':
return $this->formatItemTimestamp($value);
case 'enclosures':
return array($this->cleanImageUrl($value));
case 'categories':
return array($this->fixEncoding($value));
}
return $value;
}
/**
* Formats the title of a feed item. Takes extracted raw title and returns it formatted
* as string.
* Can be easily overwritten for in case the value needs to be transformed into something
* else.
* @param string $value
* @return string
*/
protected function formatItemTitle($value) {
return $value;
}
/**
* Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix
* timestamp as integer.
* Can be easily overwritten for example if a special format has to be expected on the
* source website.
* @param string $value
* @return string
*/
protected function formatItemContent($value) {
return $value;
}
/**
* Formats the URI of a feed item. Takes extracted raw URI and returns it formatted
* as string.
* Can be easily overwritten for in case the value needs to be transformed into something
* else.
* @param string $value
* @return string
*/
protected function formatItemUri($value) {
if(strlen($value) === 0) {
return '';
}
if(strpos($value, 'http://') === 0 || strpos($value, 'https://') === 0) {
return $value;
}
return urljoin($this->feedUri, $value);
}
/**
* Formats the author of a feed item. Takes extracted raw author and returns it formatted
* as string.
* Can be easily overwritten for in case the value needs to be transformed into something
* else.
* @param string $value
* @return string
*/
protected function formatItemAuthor($value) {
return $value;
}
/**
* Formats the timestamp of a feed item. Takes extracted raw timestamp and returns unix
* timestamp as integer.
* Can be easily overwritten for example if a special format has to be expected on the
* source website.
* @param string $value
* @return false|int
*/
protected function formatItemTimestamp($value) {
return strtotime($value);
}
/**
* Formats the enclosures of a feed item. Takes extracted raw enclosures and returns them
* formatted as array.
* Can be easily overwritten for in case the values need to be transformed into something
* else.
* @param string $value
* @return array
*/
protected function formatItemEnclosures($value) {
return array($this->cleanImageUrl($value));
}
/**
* Formats the categories of a feed item. Takes extracted raw categories and returns them
* formatted as array.
* Can be easily overwritten for in case the values need to be transformed into something
* else.
* @param string $value
* @return array
*/
protected function formatItemCategories($value) {
return array($value);
}
/**
* @param $imageUrl
* @return string|void
*/
protected function cleanImageUrl($imageUrl)
{
$result = preg_match('~(?:http(?:s)?:)?[\/a-zA-Z0-9\-_\.]+\.(?:jpg|gif|png|jpeg|ico){1}~', $imageUrl, $matches);
if(1 !== $result) {
return;
}
return urljoin($this->feedUri, $matches[0]);
}
/**
* @param $typedResult
* @return string
*/
protected function getItemValueOrNodeValue($typedResult)
{
if($typedResult instanceof DOMNodeList) {
$item = $typedResult->item(0);
if ($item instanceof DOMElement) {
return trim($item->nodeValue);
} elseif ($item instanceof DOMAttr) {
return trim($item->value);
}
} elseif(is_string($typedResult) && strlen($typedResult) > 0) {
return trim($typedResult);
}
returnServerError('Unknown type of XPath expression result.');
}
/**
* Fixes feed encoding by invoking PHP's utf8_decode function on extracted texts.
* Useful in case of "broken" or "weird" characters in the feed where you'd normally
* expect umlauts.
*
* @param $input
* @return string
*/
protected function fixEncoding($input)
{
return $this->getParam('fix_encoding') ? utf8_decode($input) : $input;
}
/**
* Allows overriding default mechanism determining items Uid's
*
* @param FeedItem $item
* @return string|null
*/
protected function generateItemId(\FeedItem $item) {
return null; //auto generation
}
}

View File

@@ -41,7 +41,7 @@
* 'content' if enabled.
*
* For more information see http://php.net/manual/en/function.curl-setopt.php
* @return string|array The contents.
* @return string The contents.
*/
function getContents($url, $header = array(), $opts = array(), $returnHeader = false){
Debug::log('Reading contents from "' . $url . '"');
@@ -198,7 +198,8 @@ EOD
if($lastError !== null)
$lastError = $lastError['message'];
returnError(<<<EOD
Unexpected response from upstream.
The requested resource cannot be found!
Please make sure your input parameters are correct!
cUrl error: $curlError ($curlErrno)
PHP error: $lastError
EOD
@@ -232,7 +233,7 @@ EOD
* when returning plaintext.
* @param string $defaultSpanText Specifies the replacement text for `<span />`
* tags when returning plaintext.
* @return false|simple_html_dom Contents as simplehtmldom object.
* @return string Contents as simplehtmldom object.
*/
function getSimpleHTMLDOM($url,
$header = array(),
@@ -282,7 +283,7 @@ function getSimpleHTMLDOM($url,
* when returning plaintext.
* @param string $defaultSpanText Specifies the replacement text for `<span />`
* tags when returning plaintext.
* @return false|simple_html_dom Contents as simplehtmldom object.
* @return string Contents as simplehtmldom object.
*/
function getSimpleHTMLDOMCached($url,
$duration = 86400,
@@ -311,7 +312,7 @@ function getSimpleHTMLDOMCached($url,
$time = $cache->getTime();
if($time !== false
&& (time() - $duration < $time)
&& !Debug::isEnabled()) { // Contents within duration
&& Debug::isEnabled()) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents($url, $header, $opts);

View File

@@ -74,7 +74,6 @@ require_once PATH_LIB . 'BridgeList.php';
require_once PATH_LIB . 'ParameterValidator.php';
require_once PATH_LIB . 'ActionFactory.php';
require_once PATH_LIB . 'ActionAbstract.php';
require_once PATH_LIB . 'XPathAbstract.php';
// Functions
require_once PATH_LIB . 'html.php';

View File

@@ -36,7 +36,7 @@ function search() {
}
if(textValue != null && uriValue != null) {
if(textValue != null || uriValue != null) {
if(textValue.match(regexMatch) != null ||
uriValue.hostname.match(regexMatch) ||

View File

@@ -360,7 +360,7 @@ h5 {
margin: 3px auto 0;
}
.info, .no-info {
.info {
display: none;
}