mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-23 08:33:24 +02:00
Compare commits
23 Commits
em92-patch
...
2020-11-10
Author | SHA1 | Date | |
---|---|---|---|
|
ff50e4918c | ||
|
8e4d6d8fdb | ||
|
cc548b16a8 | ||
|
b66026e241 | ||
|
a23d4bd0e6 | ||
|
bde4159a9e | ||
|
3ad138026d | ||
|
d05a8b79fe | ||
|
efe32aad22 | ||
|
0655b3cb39 | ||
|
59082368c7 | ||
|
c8b2c1bf74 | ||
|
b48bc77c22 | ||
|
6af87b2f32 | ||
|
93cdf5e342 | ||
|
164b407f28 | ||
|
2714c3d816 | ||
|
364b5282a3 | ||
|
5e4f3c351e | ||
|
a332a5a414 | ||
|
45e2f385b3 | ||
|
0a1ff10a52 | ||
|
645a8f62c6 |
34
README.md
34
README.md
@@ -65,6 +65,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
|||||||
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
||||||
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
|
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
|
||||||
- [`json`](https://secure.php.net/manual/en/book.json.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)
|
- [`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)
|
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||||
@@ -109,8 +110,8 @@ 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):
|
Use this script to generate the list automatically (using the GitHub API):
|
||||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* [16mhz](https://github.com/16mhz)
|
* [16mhz](https://github.com/16mhz)
|
||||||
* [86423355844265459587182778](https://github.com/86423355844265459587182778)
|
|
||||||
* [adamchainz](https://github.com/adamchainz)
|
* [adamchainz](https://github.com/adamchainz)
|
||||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||||
* [Albirew](https://github.com/Albirew)
|
* [Albirew](https://github.com/Albirew)
|
||||||
@@ -119,9 +120,12 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [alexAubin](https://github.com/alexAubin)
|
* [alexAubin](https://github.com/alexAubin)
|
||||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||||
|
* [arnd-s](https://github.com/arnd-s)
|
||||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||||
* [Astalaseven](https://github.com/Astalaseven)
|
* [Astalaseven](https://github.com/Astalaseven)
|
||||||
* [Astyan-42](https://github.com/Astyan-42)
|
* [Astyan-42](https://github.com/Astyan-42)
|
||||||
|
* [AxorPL](https://github.com/AxorPL)
|
||||||
|
* [ayacoo](https://github.com/ayacoo)
|
||||||
* [az5he6ch](https://github.com/az5he6ch)
|
* [az5he6ch](https://github.com/az5he6ch)
|
||||||
* [azdkj532](https://github.com/azdkj532)
|
* [azdkj532](https://github.com/azdkj532)
|
||||||
* [b1nj](https://github.com/b1nj)
|
* [b1nj](https://github.com/b1nj)
|
||||||
@@ -133,6 +137,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [cnlpete](https://github.com/cnlpete)
|
* [cnlpete](https://github.com/cnlpete)
|
||||||
* [corenting](https://github.com/corenting)
|
* [corenting](https://github.com/corenting)
|
||||||
* [couraudt](https://github.com/couraudt)
|
* [couraudt](https://github.com/couraudt)
|
||||||
|
* [csisoap](https://github.com/csisoap)
|
||||||
* [cyberjacob](https://github.com/cyberjacob)
|
* [cyberjacob](https://github.com/cyberjacob)
|
||||||
* [da2x](https://github.com/da2x)
|
* [da2x](https://github.com/da2x)
|
||||||
* [Daiyousei](https://github.com/Daiyousei)
|
* [Daiyousei](https://github.com/Daiyousei)
|
||||||
@@ -147,29 +152,36 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [em92](https://github.com/em92)
|
* [em92](https://github.com/em92)
|
||||||
* [eMerzh](https://github.com/eMerzh)
|
* [eMerzh](https://github.com/eMerzh)
|
||||||
* [EtienneM](https://github.com/EtienneM)
|
* [EtienneM](https://github.com/EtienneM)
|
||||||
|
* [fanch317](https://github.com/fanch317)
|
||||||
* [floviolleau](https://github.com/floviolleau)
|
* [floviolleau](https://github.com/floviolleau)
|
||||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||||
* [Frenzie](https://github.com/Frenzie)
|
* [Frenzie](https://github.com/Frenzie)
|
||||||
* [fulmeek](https://github.com/fulmeek)
|
* [fulmeek](https://github.com/fulmeek)
|
||||||
|
* [ggiessen](https://github.com/ggiessen)
|
||||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||||
* [Glandos](https://github.com/Glandos)
|
* [Glandos](https://github.com/Glandos)
|
||||||
* [gloony](https://github.com/gloony)
|
* [gloony](https://github.com/gloony)
|
||||||
* [GregThib](https://github.com/GregThib)
|
* [GregThib](https://github.com/GregThib)
|
||||||
* [griffaurel](https://github.com/griffaurel)
|
* [griffaurel](https://github.com/griffaurel)
|
||||||
* [Grummfy](https://github.com/Grummfy)
|
* [Grummfy](https://github.com/Grummfy)
|
||||||
|
* [gsantner](https://github.com/gsantner)
|
||||||
* [hunhejj](https://github.com/hunhejj)
|
* [hunhejj](https://github.com/hunhejj)
|
||||||
* [husim0](https://github.com/husim0)
|
* [husim0](https://github.com/husim0)
|
||||||
* [IceWreck](https://github.com/IceWreck)
|
* [IceWreck](https://github.com/IceWreck)
|
||||||
* [j0k3r](https://github.com/j0k3r)
|
* [j0k3r](https://github.com/j0k3r)
|
||||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||||
|
* [jannyba](https://github.com/jannyba)
|
||||||
|
* [JasonGhent](https://github.com/JasonGhent)
|
||||||
* [jdesgats](https://github.com/jdesgats)
|
* [jdesgats](https://github.com/jdesgats)
|
||||||
* [jdigilio](https://github.com/jdigilio)
|
* [jdigilio](https://github.com/jdigilio)
|
||||||
* [JeremyRand](https://github.com/JeremyRand)
|
* [JeremyRand](https://github.com/JeremyRand)
|
||||||
* [Jocker666z](https://github.com/Jocker666z)
|
* [Jocker666z](https://github.com/Jocker666z)
|
||||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||||
* [johnpc](https://github.com/johnpc)
|
* [johnpc](https://github.com/johnpc)
|
||||||
* [killruana](https://github.com/killruana)
|
* [joni1993](https://github.com/joni1993)
|
||||||
|
* [joshcoales](https://github.com/joshcoales)
|
||||||
* [klimplant](https://github.com/klimplant)
|
* [klimplant](https://github.com/klimplant)
|
||||||
|
* [kolarcz](https://github.com/kolarcz)
|
||||||
* [kranack](https://github.com/kranack)
|
* [kranack](https://github.com/kranack)
|
||||||
* [kraoc](https://github.com/kraoc)
|
* [kraoc](https://github.com/kraoc)
|
||||||
* [l1n](https://github.com/l1n)
|
* [l1n](https://github.com/l1n)
|
||||||
@@ -178,6 +190,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [lalannev](https://github.com/lalannev)
|
* [lalannev](https://github.com/lalannev)
|
||||||
* [ldidry](https://github.com/ldidry)
|
* [ldidry](https://github.com/ldidry)
|
||||||
* [Leomaradan](https://github.com/Leomaradan)
|
* [Leomaradan](https://github.com/Leomaradan)
|
||||||
|
* [liamka](https://github.com/liamka)
|
||||||
* [Limero](https://github.com/Limero)
|
* [Limero](https://github.com/Limero)
|
||||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||||
* [lorenzos](https://github.com/lorenzos)
|
* [lorenzos](https://github.com/lorenzos)
|
||||||
@@ -188,17 +201,25 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [mdemoss](https://github.com/mdemoss)
|
* [mdemoss](https://github.com/mdemoss)
|
||||||
* [melangue](https://github.com/melangue)
|
* [melangue](https://github.com/melangue)
|
||||||
* [metaMMA](https://github.com/metaMMA)
|
* [metaMMA](https://github.com/metaMMA)
|
||||||
|
* [mibe](https://github.com/mibe)
|
||||||
|
* [mightymt](https://github.com/mightymt)
|
||||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||||
|
* [mr-flibble](https://github.com/mr-flibble)
|
||||||
* [mro](https://github.com/mro)
|
* [mro](https://github.com/mro)
|
||||||
|
* [mschwld](https://github.com/mschwld)
|
||||||
* [mxmehl](https://github.com/mxmehl)
|
* [mxmehl](https://github.com/mxmehl)
|
||||||
* [nel50n](https://github.com/nel50n)
|
* [nel50n](https://github.com/nel50n)
|
||||||
* [niawag](https://github.com/niawag)
|
* [niawag](https://github.com/niawag)
|
||||||
|
* [Niehztog](https://github.com/Niehztog)
|
||||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||||
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
||||||
* [OliverParoczai](https://github.com/OliverParoczai)
|
* [OliverParoczai](https://github.com/OliverParoczai)
|
||||||
* [oratosquilla-oratoria](https://github.com/oratosquilla-oratoria)
|
* [Ololbu](https://github.com/Ololbu)
|
||||||
* [ORelio](https://github.com/ORelio)
|
* [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)
|
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||||
* [pellaeon](https://github.com/pellaeon)
|
* [pellaeon](https://github.com/pellaeon)
|
||||||
* [Piranhaplant](https://github.com/Piranhaplant)
|
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||||
@@ -208,18 +229,23 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [Pofilo](https://github.com/Pofilo)
|
* [Pofilo](https://github.com/Pofilo)
|
||||||
* [prysme01](https://github.com/prysme01)
|
* [prysme01](https://github.com/prysme01)
|
||||||
* [quentinus95](https://github.com/quentinus95)
|
* [quentinus95](https://github.com/quentinus95)
|
||||||
|
* [RawkBob](https://github.com/RawkBob)
|
||||||
* [regisenguehard](https://github.com/regisenguehard)
|
* [regisenguehard](https://github.com/regisenguehard)
|
||||||
* [Riduidel](https://github.com/Riduidel)
|
* [Riduidel](https://github.com/Riduidel)
|
||||||
* [rogerdc](https://github.com/rogerdc)
|
* [rogerdc](https://github.com/rogerdc)
|
||||||
* [Roliga](https://github.com/Roliga)
|
* [Roliga](https://github.com/Roliga)
|
||||||
|
* [ronansalmon](https://github.com/ronansalmon)
|
||||||
|
* [rremizov](https://github.com/rremizov)
|
||||||
* [sebsauvage](https://github.com/sebsauvage)
|
* [sebsauvage](https://github.com/sebsauvage)
|
||||||
* [shutosg](https://github.com/shutosg)
|
* [shutosg](https://github.com/shutosg)
|
||||||
|
* [Simounet](https://github.com/Simounet)
|
||||||
* [somini](https://github.com/somini)
|
* [somini](https://github.com/somini)
|
||||||
* [squeek502](https://github.com/squeek502)
|
* [squeek502](https://github.com/squeek502)
|
||||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||||
* [Strubbl](https://github.com/Strubbl)
|
* [Strubbl](https://github.com/Strubbl)
|
||||||
* [sublimz](https://github.com/sublimz)
|
* [sublimz](https://github.com/sublimz)
|
||||||
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
||||||
|
* [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||||
* [sysadminstory](https://github.com/sysadminstory)
|
* [sysadminstory](https://github.com/sysadminstory)
|
||||||
* [tameroski](https://github.com/tameroski)
|
* [tameroski](https://github.com/tameroski)
|
||||||
* [teromene](https://github.com/teromene)
|
* [teromene](https://github.com/teromene)
|
||||||
@@ -227,6 +253,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [thefranke](https://github.com/thefranke)
|
* [thefranke](https://github.com/thefranke)
|
||||||
* [ThePadawan](https://github.com/ThePadawan)
|
* [ThePadawan](https://github.com/ThePadawan)
|
||||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||||
|
* [theScrabi](https://github.com/theScrabi)
|
||||||
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
||||||
* [triatic](https://github.com/triatic)
|
* [triatic](https://github.com/triatic)
|
||||||
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
||||||
@@ -234,6 +261,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
|||||||
* [wtuuju](https://github.com/wtuuju)
|
* [wtuuju](https://github.com/wtuuju)
|
||||||
* [xurxof](https://github.com/xurxof)
|
* [xurxof](https://github.com/xurxof)
|
||||||
* [yardenac](https://github.com/yardenac)
|
* [yardenac](https://github.com/yardenac)
|
||||||
|
* [ymeister](https://github.com/ymeister)
|
||||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||||
|
|
||||||
Licenses
|
Licenses
|
||||||
|
@@ -10,13 +10,7 @@ class AlbionOnlineBridge extends BridgeAbstract {
|
|||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array( array(
|
||||||
'postcount' => array(
|
'postcount' => array(
|
||||||
'name' => 'Limit',
|
'name' => 'Limit',
|
||||||
'type' => 'list',
|
'type' => 'number',
|
||||||
'values' => array(
|
|
||||||
'2' => 2,
|
|
||||||
'5' => 5,
|
|
||||||
'10' => 10,
|
|
||||||
'15' => 15,
|
|
||||||
),
|
|
||||||
'title' => 'Maximum number of items to return',
|
'title' => 'Maximum number of items to return',
|
||||||
'defaultValue' => 5,
|
'defaultValue' => 5,
|
||||||
),
|
),
|
||||||
|
29
bridges/BleepingComputerBridge.php
Normal file
29
bridges/BleepingComputerBridge.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
60
bridges/BlizzardNewsBridge.php
Normal file
60
bridges/BlizzardNewsBridge.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,7 @@ class BrutBridge extends BridgeAbstract {
|
|||||||
'Entertainment' => 'entertainment',
|
'Entertainment' => 'entertainment',
|
||||||
'Sports' => 'sport',
|
'Sports' => 'sport',
|
||||||
'Nature' => 'nature',
|
'Nature' => 'nature',
|
||||||
|
'Health' => 'health',
|
||||||
),
|
),
|
||||||
'defaultValue' => 'news',
|
'defaultValue' => 'news',
|
||||||
),
|
),
|
||||||
@@ -26,6 +27,7 @@ class BrutBridge extends BridgeAbstract {
|
|||||||
'United States' => 'us',
|
'United States' => 'us',
|
||||||
'United Kingdom' => 'uk',
|
'United Kingdom' => 'uk',
|
||||||
'France' => 'fr',
|
'France' => 'fr',
|
||||||
|
'Spain' => 'es',
|
||||||
'India' => 'in',
|
'India' => 'in',
|
||||||
'Mexico' => 'mx',
|
'Mexico' => 'mx',
|
||||||
),
|
),
|
||||||
|
@@ -74,8 +74,8 @@ class CeskaTelevizeBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUri() {
|
public function getURI() {
|
||||||
return isset($this->feedUri) ? $this->feedUri : parent::getUri();
|
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName() {
|
public function getName() {
|
||||||
|
@@ -42,6 +42,7 @@ class DiarioDeNoticiasBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
return $name;
|
return $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURI() {
|
public function getURI() {
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'Tag':
|
case 'Tag':
|
||||||
|
@@ -94,6 +94,6 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getFullSizeImagePath($preview_path){
|
private function getFullSizeImagePath($preview_path){
|
||||||
return explode("?compress=1", $preview_path)[0];
|
return explode('?compress=1', $preview_path)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,14 +10,7 @@ class EpicgamesBridge extends BridgeAbstract {
|
|||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array( array(
|
||||||
'postcount' => array(
|
'postcount' => array(
|
||||||
'name' => 'Limit',
|
'name' => 'Limit',
|
||||||
'type' => 'list',
|
'type' => 'number',
|
||||||
'values' => array(
|
|
||||||
'5' => 5,
|
|
||||||
'10' => 10,
|
|
||||||
'15' => 15,
|
|
||||||
'20' => 20,
|
|
||||||
'25' => 25,
|
|
||||||
),
|
|
||||||
'title' => 'Maximum number of items to return',
|
'title' => 'Maximum number of items to return',
|
||||||
'defaultValue' => 10,
|
'defaultValue' => 10,
|
||||||
),
|
),
|
||||||
|
@@ -28,9 +28,9 @@ class FM4Bridge extends BridgeAbstract
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
public function getPageData($tag, $page) {
|
private function getPageData($tag, $page) {
|
||||||
if($tag)
|
if($tag)
|
||||||
$uri = self::URI . "/tags/" . $tag;
|
$uri = self::URI . '/tags/' . $tag;
|
||||||
else
|
else
|
||||||
$uri = self::URI;
|
$uri = self::URI;
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class FM4Bridge extends BridgeAbstract
|
|||||||
$item['uri'] = $article->find('a', 0)->href;
|
$item['uri'] = $article->find('a', 0)->href;
|
||||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||||
$item['author'] = $article->find('p[class*=keyword]', 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')) {
|
if ($this->getInput('loadcontent')) {
|
||||||
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
|
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
|
||||||
@@ -58,9 +58,10 @@ class FM4Bridge extends BridgeAbstract
|
|||||||
}
|
}
|
||||||
return $page_items;
|
return $page_items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectData() {
|
public function collectData() {
|
||||||
for ($cur_page = 1; $cur_page <= $this->getInput('pages'); $cur_page++) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -175,7 +175,13 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
$header = array();
|
$header = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
$touchURI = str_replace(
|
||||||
|
'https://www.facebook',
|
||||||
|
'https://touch.facebook',
|
||||||
|
$this->getURI()
|
||||||
|
);
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM($touchURI, $header)
|
||||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||||
|
|
||||||
if(!$this->isPublicGroup($html)) {
|
if(!$this->isPublicGroup($html)) {
|
||||||
@@ -186,19 +192,18 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$this->groupName = $this->extractGroupName($html);
|
$this->groupName = $this->extractGroupName($html);
|
||||||
|
|
||||||
$posts = $html->find('div.userContentWrapper')
|
$posts = $html->find('div.story_body_container')
|
||||||
or returnServerError('Failed finding posts!');
|
or returnServerError('Failed finding posts!');
|
||||||
|
|
||||||
foreach($posts as $post) {
|
foreach($posts as $post) {
|
||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
|
|
||||||
$item['uri'] = $this->extractGroupURI($post);
|
$item['uri'] = $this->extractGroupPostURI($post);
|
||||||
$item['title'] = $this->extractGroupTitle($post);
|
$item['title'] = $this->extractGroupPostTitle($post);
|
||||||
$item['author'] = $this->extractGroupAuthor($post);
|
$item['author'] = $this->extractGroupPostAuthor($post);
|
||||||
$item['content'] = $this->extractGroupContent($post);
|
$item['content'] = $this->extractGroupPostContent($post);
|
||||||
$item['timestamp'] = $this->extractGroupTimestamp($post);
|
$item['enclosures'] = $this->extractGroupPostEnclosures($post);
|
||||||
$item['enclosures'] = $this->extractGroupEnclosures($post);
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
|
|
||||||
@@ -215,16 +220,7 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$urlparts = parse_url($group);
|
$urlparts = parse_url($group);
|
||||||
|
|
||||||
if($urlparts['host'] !== parse_url(self::URI)['host']
|
$this->validateHost($urlparts['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];
|
return explode('/', $urlparts['path'])[2];
|
||||||
|
|
||||||
@@ -236,24 +232,47 @@ 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) {
|
private function isPublicGroup($html) {
|
||||||
|
|
||||||
// Facebook redirects to the groups about page for non-public groups
|
// Facebook touch just presents a login page for non-public groups
|
||||||
$about = $html->find('#pagelet_group_about', 0);
|
$title = $html->find('title', 0);
|
||||||
|
return $title->plaintext !== 'Log in to Facebook | Facebook';
|
||||||
return !($about);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupName($html) {
|
private function extractGroupName($html) {
|
||||||
|
|
||||||
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
$ogtitle = $html->find('._de1', 0)
|
||||||
or returnServerError('Unable to find group title!');
|
or returnServerError('Unable to find group title!');
|
||||||
|
|
||||||
return html_entity_decode($ogtitle->content, ENT_QUOTES);
|
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupURI($post) {
|
private function extractGroupPostURI($post) {
|
||||||
|
|
||||||
$elements = $post->find('a')
|
$elements = $post->find('a')
|
||||||
or returnServerError('Unable to find URI!');
|
or returnServerError('Unable to find URI!');
|
||||||
@@ -262,7 +281,8 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
// Find the one that is a permalink
|
// Find the one that is a permalink
|
||||||
if(strpos($anchor->href, 'permalink') !== false) {
|
if(strpos($anchor->href, 'permalink') !== false) {
|
||||||
return $anchor->href;
|
$arr = explode('?', $anchor->href, 2);
|
||||||
|
return $arr[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -271,57 +291,61 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupContent($post) {
|
private function extractGroupPostContent($post) {
|
||||||
|
|
||||||
$content = $post->find('div.userContent', 0)
|
$content = $post->find('div._5rgt', 0)
|
||||||
or returnServerError('Unable to find user content!');
|
or returnServerError('Unable to find user content!');
|
||||||
|
|
||||||
return $content->innertext . $content->next_sibling()->innertext;
|
$context_text = $content->innertext;
|
||||||
|
if ($content->next_sibling() !== null) {
|
||||||
|
$context_text .= $content->next_sibling()->innertext;
|
||||||
|
}
|
||||||
|
return $context_text;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupTimestamp($post) {
|
private function extractGroupPostAuthor($post) {
|
||||||
|
|
||||||
$element = $post->find('abbr[data-utime]', 0)
|
$element = $post->find('h3 a', 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!');
|
or returnServerError('Unable to find author information!');
|
||||||
|
|
||||||
return $element->{'aria-label'};
|
return $element->plaintext;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupEnclosures($post) {
|
private function extractGroupPostEnclosures($post) {
|
||||||
|
|
||||||
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
|
$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'));
|
||||||
|
}
|
||||||
|
|
||||||
$enclosures = array();
|
$enclosures = array();
|
||||||
|
|
||||||
|
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
|
||||||
|
|
||||||
foreach($elements as $enclosure) {
|
foreach($elements as $enclosure) {
|
||||||
$enclosures[] = $enclosure->src;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty($enclosures) ? null : $enclosures;
|
return empty($enclosures) ? null : $enclosures;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractGroupTitle($post) {
|
private function extractGroupPostTitle($post) {
|
||||||
|
|
||||||
$element = $post->find('h5', 0)
|
$element = $post->find('h3', 0)
|
||||||
or returnServerError('Unable to find title!');
|
or returnServerError('Unable to find title!');
|
||||||
|
|
||||||
if(strpos($element->plaintext, 'shared') === false) {
|
if(strpos($element->plaintext, 'shared') === false) {
|
||||||
|
|
||||||
$content = strip_tags($this->extractGroupContent($post));
|
$content = strip_tags($this->extractGroupPostContent($post));
|
||||||
|
|
||||||
return $this->extractGroupAuthor($post)
|
return $this->extractGroupPostAuthor($post)
|
||||||
. ' posted: '
|
. ' posted: '
|
||||||
. substr(
|
. substr(
|
||||||
$content,
|
$content,
|
||||||
@@ -348,13 +372,7 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$urlparts = parse_url($user);
|
$urlparts = parse_url($user);
|
||||||
|
|
||||||
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
$this->validateHost($urlparts['host']);
|
||||||
returnClientError('The host you provided is invalid! Received "'
|
|
||||||
. $urlparts['host']
|
|
||||||
. '", expected "'
|
|
||||||
. parse_url(self::URI)['host']
|
|
||||||
. '"!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!array_key_exists('path', $urlparts)
|
if(!array_key_exists('path', $urlparts)
|
||||||
|| $urlparts['path'] === '/') {
|
|| $urlparts['path'] === '/') {
|
||||||
@@ -555,7 +573,7 @@ EOD;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No captcha? We can carry on retrieving page contents :)
|
// No captcha? We can carry on retrieving page contents :)
|
||||||
// First, we check wether the page is public or not
|
// First, we check whether the page is public or not
|
||||||
$loginForm = $html->find('._585r', 0);
|
$loginForm = $html->find('._585r', 0);
|
||||||
|
|
||||||
if($loginForm != null) {
|
if($loginForm != null) {
|
||||||
|
@@ -82,7 +82,7 @@ class FicbookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$html = defaultLinkTo($html, self::URI);
|
$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;
|
$this->titleName = $html->find('.fanfic-main-info > h1', 0)->innertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
|||||||
$t = $element->find('a[href]', 0)->href;
|
$t = $element->find('a[href]', 0)->href;
|
||||||
$item['uri'] = htmlspecialchars_decode($t);
|
$item['uri'] = htmlspecialchars_decode($t);
|
||||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||||
$item['content'] = $element->find('span[class=st]', 0)->plaintext;
|
$item['content'] = $element->find('span[class=aCOpRe]', 0)->plaintext;
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
|
@@ -45,8 +45,10 @@ class HeiseBridge extends FeedExpander {
|
|||||||
$article = getSimpleHTMLDOMCached($uri)
|
$article = getSimpleHTMLDOMCached($uri)
|
||||||
or returnServerError('Could not open article: ' . $uri);
|
or returnServerError('Could not open article: ' . $uri);
|
||||||
|
|
||||||
$article = defaultLinkTo($article, $uri);
|
if ($article) {
|
||||||
$item = $this->addArticleToItem($item, $article);
|
$article = defaultLinkTo($article, $uri);
|
||||||
|
$item = $this->addArticleToItem($item, $article);
|
||||||
|
}
|
||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ class KoreusBridge extends FeedExpander {
|
|||||||
|
|
||||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||||
const NAME = 'Koreus';
|
const NAME = 'Koreus';
|
||||||
const URI = 'http://www.koreus.com/';
|
const URI = 'https://www.koreus.com/';
|
||||||
const DESCRIPTION = 'Returns the newest posts from Koreus (full text)';
|
const DESCRIPTION = 'Returns the newest posts from Koreus (full text)';
|
||||||
|
|
||||||
protected function parseItem($item){
|
protected function parseItem($item){
|
||||||
@@ -17,6 +17,6 @@ class KoreusBridge extends FeedExpander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$this->collectExpandableDatas('http://feeds.feedburner.com/Koreus-articles');
|
$this->collectExpandableDatas('https://feeds.feedburner.com/Koreus-articles');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
bridges/MallTvBridge.php
Normal file
73
bridges/MallTvBridge.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@@ -103,8 +103,8 @@ class MarktplaatsBridge extends BridgeAbstract {
|
|||||||
$item['content'] .= "<br />\n<br />\n<br />\n" . json_encode($listing);
|
$item['content'] .= "<br />\n<br />\n<br />\n" . json_encode($listing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$item['content'] .= "<br>\n<br>\nPrice: " . $listing->priceInfo->priceCents/100;
|
$item['content'] .= "<br>\n<br>\nPrice: " . $listing->priceInfo->priceCents / 100;
|
||||||
$item['content'] .= " (" . $listing->priceInfo->priceType .")";
|
$item['content'] .= ' (' . $listing->priceInfo->priceType . ')';
|
||||||
if(!empty($listing->location->cityName)) {
|
if(!empty($listing->location->cityName)) {
|
||||||
$item['content'] .= "<br><br>\n" . $listing->location->cityName;
|
$item['content'] .= "<br><br>\n" . $listing->location->cityName;
|
||||||
}
|
}
|
||||||
@@ -119,9 +119,9 @@ class MarktplaatsBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
if(!is_null($this->getInput('q'))) {
|
if(!is_null($this->getInput('q'))) {
|
||||||
return $this->getInput('q') . ' - Marktplaats';
|
return $this->getInput('q') . ' - Marktplaats';
|
||||||
}
|
}
|
||||||
return parent::getName();
|
return parent::getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,9 @@ class MondeDiploBridge extends BridgeAbstract {
|
|||||||
const CACHE_TIMEOUT = 21600; //6h
|
const CACHE_TIMEOUT = 21600; //6h
|
||||||
const DESCRIPTION = 'Returns most recent results from MondeDiplo.';
|
const DESCRIPTION = 'Returns most recent results from MondeDiplo.';
|
||||||
|
|
||||||
private function cleanText($text) {
|
private function cleanText($text) {
|
||||||
return trim(str_replace([' ', ' '], ' ', $text));
|
return trim(str_replace(array(' ', ' '), ' ', $text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$html = getSimpleHTMLDOM(self::URI)
|
$html = getSimpleHTMLDOM(self::URI)
|
||||||
@@ -22,7 +22,7 @@ class MondeDiploBridge extends BridgeAbstract {
|
|||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = self::URI . $element->href;
|
$item['uri'] = self::URI . $element->href;
|
||||||
$item['title'] = $this->cleanText($title) . ' - ' . $this->cleanText($datesAuteurs);
|
$item['title'] = $this->cleanText($title) . ' - ' . $this->cleanText($datesAuteurs);
|
||||||
$item['content'] = $this->cleanText(str_replace([$title, $datesAuteurs], '', $element->plaintext));
|
$item['content'] = $this->cleanText(str_replace(array($title, $datesAuteurs), '', $element->plaintext));
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ class NasaApodBridge extends BridgeAbstract {
|
|||||||
$picture_html_string = $picture_html->innertext;
|
$picture_html_string = $picture_html->innertext;
|
||||||
|
|
||||||
//Extract image and explanation
|
//Extract image and explanation
|
||||||
$image_wrapper = $picture_html->find('a',1);
|
$image_wrapper = $picture_html->find('a', 1);
|
||||||
$image_path = $image_wrapper->href;
|
$image_path = $image_wrapper->href;
|
||||||
$img_placeholder = $image_wrapper->find('img', 0);
|
$img_placeholder = $image_wrapper->find('img', 0);
|
||||||
$img_alt = $img_placeholder->alt;
|
$img_alt = $img_placeholder->alt;
|
||||||
|
@@ -148,7 +148,7 @@ class NineGagBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$AvoidElement) {
|
if (!$AvoidElement) {
|
||||||
$item['uri'] = $post['url'];
|
$item['uri'] = preg_replace('/^http:/i', 'https:', $post['url']);
|
||||||
$item['title'] = $post['title'];
|
$item['title'] = $post['title'];
|
||||||
$item['content'] = self::getContent($post);
|
$item['content'] = self::getContent($post);
|
||||||
$item['categories'] = self::getCategories($post);
|
$item['categories'] = self::getCategories($post);
|
||||||
|
@@ -48,11 +48,16 @@ class NordbayernBridge extends BridgeAbstract {
|
|||||||
));
|
));
|
||||||
|
|
||||||
private function getImageUrlFromScript($script) {
|
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])) {
|
if(isset($matches[1][0])) {
|
||||||
return stripcslashes($matches[1][0]) . '?w=800';
|
return stripcslashes($matches[1][0]) . '?w=800';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleArticle($link) {
|
private function handleArticle($link) {
|
||||||
|
37
bridges/OpenwrtSecurityBridge.php
Normal file
37
bridges/OpenwrtSecurityBridge.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
@@ -11,13 +11,11 @@ class OtrkeyFinderBridge extends BridgeAbstract {
|
|||||||
'searchterm' => array(
|
'searchterm' => array(
|
||||||
'name' => 'Search term',
|
'name' => 'Search term',
|
||||||
'exampleValue' => 'Terminator',
|
'exampleValue' => 'Terminator',
|
||||||
'defaultValue' => '',
|
|
||||||
'title' => 'The search term is case-insensitive',
|
'title' => 'The search term is case-insensitive',
|
||||||
),
|
),
|
||||||
'station' => array(
|
'station' => array(
|
||||||
'name' => 'Station name',
|
'name' => 'Station name',
|
||||||
'exampleValue' => 'ARD',
|
'exampleValue' => 'ARD',
|
||||||
'defaultValue' => '',
|
|
||||||
),
|
),
|
||||||
'type' => array(
|
'type' => array(
|
||||||
'name' => 'Media type',
|
'name' => 'Media type',
|
||||||
@@ -143,7 +141,11 @@ class OtrkeyFinderBridge extends BridgeAbstract {
|
|||||||
$item['content'] = $content;
|
$item['content'] = $content;
|
||||||
|
|
||||||
if (preg_match(self::TIME_REGEX, $file, $matches) === 1) {
|
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;
|
return $item;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
// This bridge depends on Releases3DSBridge
|
// This bridge depends on Releases3DSBridge
|
||||||
if (!class_exists('Releases3DSBridge')){
|
if (!class_exists('Releases3DSBridge')) {
|
||||||
include('Releases3DSBridge.php');
|
include('Releases3DSBridge.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,9 @@ class TwitchBridge extends BridgeAbstract {
|
|||||||
'All' => 'all',
|
'All' => 'all',
|
||||||
'Archive' => 'archive',
|
'Archive' => 'archive',
|
||||||
'Highlights' => 'highlight',
|
'Highlights' => 'highlight',
|
||||||
'Uploads' => 'upload'
|
'Uploads' => 'upload',
|
||||||
|
'Past Premieres' => 'past_premiere',
|
||||||
|
'Premiere Uploads' => 'premiere_upload'
|
||||||
),
|
),
|
||||||
'defaultValue' => 'archive'
|
'defaultValue' => 'archive'
|
||||||
)
|
)
|
||||||
@@ -32,43 +34,90 @@ class TwitchBridge extends BridgeAbstract {
|
|||||||
*/
|
*/
|
||||||
const CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
|
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(){
|
public function collectData(){
|
||||||
// get channel user
|
$query = <<<'EOD'
|
||||||
$query_data = array(
|
query VODList($channel: String!, $types: [BroadcastType!]) {
|
||||||
'login' => $this->getInput('channel')
|
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')]
|
||||||
);
|
);
|
||||||
$users = $this->apiGet('users', $query_data)->users;
|
$data = $this->apiRequest($query, $variables);
|
||||||
if(count($users) === 0)
|
|
||||||
returnClientError('User "'
|
|
||||||
. $this->getInput('channel')
|
|
||||||
. '" could not be found');
|
|
||||||
$user = $users[0];
|
|
||||||
|
|
||||||
// get video list
|
$user = $data->user;
|
||||||
$query_endpoint = 'channels/' . $user->_id . '/videos';
|
foreach($user->videos->edges as $edge) {
|
||||||
$query_data = array(
|
$video = $edge->node;
|
||||||
'broadcast_type' => $this->getInput('type'),
|
|
||||||
'limit' => 10
|
$url = 'https://www.twitch.tv/videos/' . $video->id;
|
||||||
);
|
|
||||||
$videos = $this->apiGet($query_endpoint, $query_data)->videos;
|
|
||||||
|
|
||||||
foreach($videos as $video) {
|
|
||||||
$item = array(
|
$item = array(
|
||||||
'uri' => $video->url,
|
'uri' => $url,
|
||||||
'title' => $video->title,
|
'title' => $video->title,
|
||||||
'timestamp' => $video->published_at,
|
'timestamp' => $video->publishedAt,
|
||||||
'author' => $video->channel->display_name,
|
'author' => $user->displayName,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add categories for tags and played game
|
// Add categories for tags and played game
|
||||||
$item['categories'] = array_filter(explode(' ', $video->tag_list));
|
$item['categories'] = $video->tags;
|
||||||
if(!empty($video->game))
|
if(!is_null($video->game))
|
||||||
$item['categories'][] = $video->game;
|
$item['categories'][] = $video->game->displayName;
|
||||||
|
foreach($video->contentTags as $tag)
|
||||||
|
if(!$tag->isLanguageTag)
|
||||||
|
$item['categories'][] = $tag->localizedName;
|
||||||
|
|
||||||
// Add enclosures for thumbnails from a few points in the video
|
// Add enclosures for thumbnails from a few points in the video
|
||||||
$item['enclosures'] = array();
|
// Thumbnail list has duplicate entries sometimes so remove those
|
||||||
foreach($video->thumbnails->large as $thumbnail)
|
$item['enclosures'] = array_unique($video->thumbnailURLs);
|
||||||
$item['enclosures'][] = $thumbnail->url;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Content format example:
|
* Content format example:
|
||||||
@@ -86,44 +135,45 @@ class TwitchBridge extends BridgeAbstract {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
$item['content'] = '<p><a href="'
|
$item['content'] = '<p><a href="'
|
||||||
. $video->url
|
. $url
|
||||||
. '"><img src="'
|
. '"><img src="'
|
||||||
. $video->preview->large
|
. $video->previewThumbnailURL
|
||||||
. '" /></a></p><p>'
|
. '" /></a></p><p>'
|
||||||
. $video->description_html
|
. $video->description // in markdown format
|
||||||
. '</p><p><b>Duration:</b> '
|
. '</p><p><b>Duration:</b> '
|
||||||
. $this->formatTimestampTime($video->length)
|
. $this->formatTimestampTime($video->lengthSeconds)
|
||||||
. '<br/><b>Views:</b> '
|
. '<br/><b>Views:</b> '
|
||||||
. $video->views
|
. $video->viewCount
|
||||||
. '</p>';
|
. '</p>';
|
||||||
|
|
||||||
// Add played games list to content
|
// Add played games list to content
|
||||||
$video_id = trim($video->_id, 'v'); // _id gives 'v1234' but API wants '1234'
|
$item['content'] .= '<p><b>Played games:</b><ul>';
|
||||||
$markers = $this->apiGet('videos/' . $video_id . '/markers')->markers;
|
if(count($video->moments->edges) > 0) {
|
||||||
$item['content'] .= '<p><b>Played games:</b></b><ul><li><a href="'
|
foreach($video->moments->edges as $edge) {
|
||||||
. $video->url
|
$moment = $edge->node;
|
||||||
. '">00:00:00</a> - '
|
|
||||||
. $video->game
|
$item['categories'][] = $moment->description;
|
||||||
. '</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="'
|
$item['content'] .= '<li><a href="'
|
||||||
. $video->url
|
. $url
|
||||||
. '?t='
|
. '?t='
|
||||||
. $this->formatQueryTime($game_change->time)
|
. $this->formatQueryTime($moment->positionMilliseconds / 1000)
|
||||||
. '">'
|
. '">'
|
||||||
. $this->formatTimestampTime($game_change->time)
|
. $this->formatTimestampTime($moment->positionMilliseconds / 1000)
|
||||||
. '</a> - '
|
. '</a> - '
|
||||||
. $game_change->label
|
. $moment->description
|
||||||
. '</li>';
|
. '</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['content'] .= '</ul></p>';
|
||||||
|
|
||||||
|
$item['categories'] = array_unique($item['categories']);
|
||||||
|
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,25 +194,37 @@ class TwitchBridge extends BridgeAbstract {
|
|||||||
$seconds % 60);
|
$seconds % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// GraphQL: https://graphql.org/
|
||||||
* Ideally the new 'helix' API should be used as v5/'kraken' is deprecated.
|
// Tool for developing/testing queries: https://github.com/skevy/graphiql-app
|
||||||
* The new API however still misses many features (markers, played game..) of
|
private function apiRequest($query, $variables) {
|
||||||
* the old one, so let's use the old one for as long as it's available.
|
$request = array(
|
||||||
*/
|
'query' => $query,
|
||||||
private function apiGet($endpoint, $query_data = array()) {
|
'variables' => $variables
|
||||||
$query_data['api_version'] = 5;
|
);
|
||||||
$url = 'https://api.twitch.tv/kraken/'
|
|
||||||
. $endpoint
|
|
||||||
. '?'
|
|
||||||
. http_build_query($query_data);
|
|
||||||
$header = array(
|
$header = array(
|
||||||
'Client-ID: ' . self::CLIENT_ID
|
'Client-ID: ' . self::CLIENT_ID
|
||||||
);
|
);
|
||||||
|
$opts = array(
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($request)
|
||||||
|
);
|
||||||
|
|
||||||
$data = json_decode(getContents($url, $header))
|
Debug::log("Sending GraphQL query:\n" . $query);
|
||||||
or returnServerError('API request to "' . $url . '" failed.');
|
Debug::log("Sending GraphQL variables:\n"
|
||||||
|
. json_encode($variables, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
return $data;
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
|
@@ -95,6 +95,20 @@ EOD
|
|||||||
'required' => false,
|
'required' => false,
|
||||||
'title' => 'Specify term to search for'
|
'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'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -145,6 +159,8 @@ EOD
|
|||||||
break;
|
break;
|
||||||
case 'By list':
|
case 'By list':
|
||||||
return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user');
|
return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user');
|
||||||
|
case 'By list ID':
|
||||||
|
return 'Twitter List #' . $this->getInput('listid');
|
||||||
default: return parent::getName();
|
default: return parent::getName();
|
||||||
}
|
}
|
||||||
return 'Twitter ' . $specific . $this->getInput($param);
|
return 'Twitter ' . $specific . $this->getInput($param);
|
||||||
@@ -167,6 +183,10 @@ EOD
|
|||||||
. urlencode($this->getInput('user'))
|
. urlencode($this->getInput('user'))
|
||||||
. '/lists/'
|
. '/lists/'
|
||||||
. str_replace(' ', '-', strtolower($this->getInput('list')));
|
. str_replace(' ', '-', strtolower($this->getInput('list')));
|
||||||
|
case 'By list ID':
|
||||||
|
return self::URI
|
||||||
|
. 'i/lists/'
|
||||||
|
. urlencode($this->getInput('listid'));
|
||||||
default: return parent::getURI();
|
default: return parent::getURI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +208,11 @@ EOD
|
|||||||
. '/2/timeline/list.json?list_id='
|
. '/2/timeline/list.json?list_id='
|
||||||
. $this->getListId($this->getInput('user'), $this->getInput('list'))
|
. $this->getListId($this->getInput('user'), $this->getInput('list'))
|
||||||
. '&tweet_mode=extended';
|
. '&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 !');
|
default: returnServerError('Invalid query context !');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +379,7 @@ EOD;
|
|||||||
|
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'By list':
|
case 'By list':
|
||||||
|
case 'By list ID':
|
||||||
// Check if filter applies to list (using raw content)
|
// Check if filter applies to list (using raw content)
|
||||||
if($this->getInput('filter')) {
|
if($this->getInput('filter')) {
|
||||||
if(stripos($cleanedTweet, $this->getInput('filter')) === false) {
|
if(stripos($cleanedTweet, $this->getInput('filter')) === false) {
|
||||||
|
@@ -8,7 +8,7 @@ class VarietyBridge extends FeedExpander {
|
|||||||
const DESCRIPTION = 'RSS feed for Variety';
|
const DESCRIPTION = 'RSS feed for Variety';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$this->collectExpandableDatas('http://feeds.feedburner.com/variety/headlines', 15);
|
$this->collectExpandableDatas('https://feeds.feedburner.com/variety/headlines', 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function parseItem($newsItem){
|
protected function parseItem($newsItem){
|
||||||
|
@@ -92,9 +92,9 @@ class WordPressBridge extends FeedExpander {
|
|||||||
returnClientError('The url parameter must either refer to http or https protocol.');
|
returnClientError('The url parameter must either refer to http or https protocol.');
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
|
$this->collectExpandableDatas($this->getURI() . '/feed/atom/', 20);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
|
$this->collectExpandableDatas($this->getURI() . '/?feed=atom', 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
class WorldCosplayBridge extends BridgeAbstract {
|
class WorldCosplayBridge extends BridgeAbstract {
|
||||||
const NAME = 'WorldCosplay Bridge';
|
const NAME = 'WorldCosplay Bridge';
|
||||||
const URI = 'https://worldcosplay.net/';
|
const URI = 'https://worldcosplay.net/';
|
||||||
const DESCRIPTION ='Returns WorldCosplay photos';
|
const DESCRIPTION = 'Returns WorldCosplay photos';
|
||||||
const MAINTAINER = 'AxorPL';
|
const MAINTAINER = 'AxorPL';
|
||||||
|
|
||||||
const API_CHARACTER = 'api/photo/list.json?character_id=%u&limit=%u';
|
const API_CHARACTER = 'api/photo/list.json?character_id=%u&limit=%u';
|
||||||
@@ -95,14 +95,12 @@ class WorldCosplayBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$json = json_decode(getContents($url))
|
$json = json_decode(getContents($url))
|
||||||
or returnServerError(sprintf(self::ERR_QUERY, $url));
|
or returnServerError(sprintf(self::ERR_QUERY, $url));
|
||||||
if($json->has_error)
|
if($json->has_error) {
|
||||||
{
|
|
||||||
returnServerError($json->message);
|
returnServerError($json->message);
|
||||||
}
|
}
|
||||||
$list = $json->list;
|
$list = $json->list;
|
||||||
|
|
||||||
foreach($list as $img)
|
foreach($list as $img) {
|
||||||
{
|
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = self::URI . substr($img->photo->url, 1);
|
$item['uri'] = self::URI . substr($img->photo->url, 1);
|
||||||
$item['title'] = $img->photo->subject;
|
$item['title'] = $img->photo->subject;
|
||||||
|
251
bridges/XPathBridge.php
Normal file
251
bridges/XPathBridge.php
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?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 <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.
|
||||||
|
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 <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.
|
||||||
|
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 <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.
|
||||||
|
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 <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.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@@ -34,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-memcached": "Allows to use memcached as cache type",
|
"ext-memcached": "Allows to use memcached as cache type",
|
||||||
"ext-sqlite3": "Allows to use an SQLite database for caching"
|
"ext-sqlite3": "Allows to use an SQLite database for caching",
|
||||||
|
"ext-dom": "Allows to use some bridges based on XPath expressions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -126,7 +126,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
if(isset($inputEntry['title']))
|
if(isset($inputEntry['title']))
|
||||||
$form .= '<i class="info" title="' . filter_var($inputEntry['title'], FILTER_SANITIZE_STRING) . '">i</i>';
|
$form .= '<i class="info" title="' . filter_var($inputEntry['title'], FILTER_SANITIZE_STRING) . '">i</i>';
|
||||||
else
|
else
|
||||||
$form .= '<i></i>';
|
$form .= '<i class="no-info"></i>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$form .= '</div>';
|
$form .= '</div>';
|
||||||
|
@@ -129,7 +129,7 @@ EOD;
|
|||||||
* @return string The searchbar
|
* @return string The searchbar
|
||||||
*/
|
*/
|
||||||
private static function getSearchbar() {
|
private static function getSearchbar() {
|
||||||
$query = filter_input(INPUT_GET, 'q');
|
$query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||||
|
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
<section class="searchbar">
|
<section class="searchbar">
|
||||||
|
@@ -28,7 +28,7 @@ final class Configuration {
|
|||||||
*
|
*
|
||||||
* @todo Replace this property by a constant.
|
* @todo Replace this property by a constant.
|
||||||
*/
|
*/
|
||||||
public static $VERSION = 'dev.2020-02-26';
|
public static $VERSION = 'dev.2020-11-10';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the configuration data.
|
* Holds the configuration data.
|
||||||
|
583
lib/XPathAbstract.php
Normal file
583
lib/XPathAbstract.php
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
<?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
|
||||||
|
}
|
||||||
|
}
|
@@ -41,7 +41,7 @@
|
|||||||
* 'content' if enabled.
|
* 'content' if enabled.
|
||||||
*
|
*
|
||||||
* For more information see http://php.net/manual/en/function.curl-setopt.php
|
* For more information see http://php.net/manual/en/function.curl-setopt.php
|
||||||
* @return string The contents.
|
* @return string|array The contents.
|
||||||
*/
|
*/
|
||||||
function getContents($url, $header = array(), $opts = array(), $returnHeader = false){
|
function getContents($url, $header = array(), $opts = array(), $returnHeader = false){
|
||||||
Debug::log('Reading contents from "' . $url . '"');
|
Debug::log('Reading contents from "' . $url . '"');
|
||||||
@@ -233,7 +233,7 @@ EOD
|
|||||||
* when returning plaintext.
|
* when returning plaintext.
|
||||||
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
||||||
* tags when returning plaintext.
|
* tags when returning plaintext.
|
||||||
* @return string Contents as simplehtmldom object.
|
* @return false|simple_html_dom Contents as simplehtmldom object.
|
||||||
*/
|
*/
|
||||||
function getSimpleHTMLDOM($url,
|
function getSimpleHTMLDOM($url,
|
||||||
$header = array(),
|
$header = array(),
|
||||||
@@ -283,7 +283,7 @@ function getSimpleHTMLDOM($url,
|
|||||||
* when returning plaintext.
|
* when returning plaintext.
|
||||||
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
||||||
* tags when returning plaintext.
|
* tags when returning plaintext.
|
||||||
* @return string Contents as simplehtmldom object.
|
* @return false|simple_html_dom Contents as simplehtmldom object.
|
||||||
*/
|
*/
|
||||||
function getSimpleHTMLDOMCached($url,
|
function getSimpleHTMLDOMCached($url,
|
||||||
$duration = 86400,
|
$duration = 86400,
|
||||||
|
@@ -74,6 +74,7 @@ require_once PATH_LIB . 'BridgeList.php';
|
|||||||
require_once PATH_LIB . 'ParameterValidator.php';
|
require_once PATH_LIB . 'ParameterValidator.php';
|
||||||
require_once PATH_LIB . 'ActionFactory.php';
|
require_once PATH_LIB . 'ActionFactory.php';
|
||||||
require_once PATH_LIB . 'ActionAbstract.php';
|
require_once PATH_LIB . 'ActionAbstract.php';
|
||||||
|
require_once PATH_LIB . 'XPathAbstract.php';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
require_once PATH_LIB . 'html.php';
|
require_once PATH_LIB . 'html.php';
|
||||||
|
@@ -360,7 +360,7 @@ h5 {
|
|||||||
margin: 3px auto 0;
|
margin: 3px auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info, .no-info {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user