mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-17 14:00:43 +02:00
Compare commits
122 Commits
2023-03-22
...
2023-07-11
Author | SHA1 | Date | |
---|---|---|---|
|
6c0e186d3f | ||
|
c9a861e259 | ||
|
dfe78fb379 | ||
|
f0a504bb9a | ||
|
7881c87bed | ||
|
1a529fac46 | ||
|
adc38e65d9 | ||
|
0b95dc2d4f | ||
|
61b307a9f9 | ||
|
341649a8a4 | ||
|
91976f7d56 | ||
|
8b996e3056 | ||
|
c1c8304fc0 | ||
|
b594ad2de3 | ||
|
ef0b86968c | ||
|
d49ea235f0 | ||
|
46f0e97c73 | ||
|
5e22459eb6 | ||
|
965d7d44c5 | ||
|
21c8d8775e | ||
|
caac7f572c | ||
|
f8801d8cb3 | ||
|
8f9147458d | ||
|
a9fd3b9e61 | ||
|
18e1597361 | ||
|
e9af41d666 | ||
|
354317d010 | ||
|
84501cfc00 | ||
|
82c22bd2b5 | ||
|
a21d496bc7 | ||
|
cf920694d5 | ||
|
bf0d771367 | ||
|
48385777b4 | ||
|
d8bc015efc | ||
|
0f14a0f6ee | ||
|
eb2b4747ae | ||
|
bf73372d7f | ||
|
748fc9fd65 | ||
|
372880b5ef | ||
|
cc91ee1e37 | ||
|
fece9ed344 | ||
|
b6a263037a | ||
|
410ef85618 | ||
|
8eabdbe5f8 | ||
|
bd6f56383c | ||
|
d4bc63ee98 | ||
|
1b02d4f49b | ||
|
a4ed52ca30 | ||
|
1769399da8 | ||
|
12ba6154f9 | ||
|
8e35ebf482 | ||
|
61130e89b4 | ||
|
ebebb886c5 | ||
|
6eaa31b999 | ||
|
1d3888f22a | ||
|
60be4cdebd | ||
|
5a0bacbd8a | ||
|
0c808dc3a1 | ||
|
98b72b2c5c | ||
|
54d626d5cd | ||
|
1e470ef341 | ||
|
0a8fe57003 | ||
|
d9490c6518 | ||
|
eb799e59a6 | ||
|
ec1a3f4fe3 | ||
|
80376830c5 | ||
|
e859497d6a | ||
|
ca351edbfe | ||
|
8f9eaae338 | ||
|
fbaf26e8bf | ||
|
95071d0134 | ||
|
3f8165207e | ||
|
08be0ad7a5 | ||
|
6fa1f349d9 | ||
|
54957d2a03 | ||
|
819e453064 | ||
|
1636a84c25 | ||
|
ee498eadf9 | ||
|
c5cd229445 | ||
|
845a8f7936 | ||
|
2f0784c287 | ||
|
227c7b8968 | ||
|
01f731cfa4 | ||
|
87b9f2dd94 | ||
|
f803ffa79a | ||
|
b5dbec4cc1 | ||
|
3e0d024888 | ||
|
cfe81ab2ac | ||
|
096c3bca73 | ||
|
ecd717cf58 | ||
|
0c540b4637 | ||
|
d0f7f5e2d8 | ||
|
e99e026fa8 | ||
|
50865d5741 | ||
|
dc4134ed1d | ||
|
c6c4b3a24f | ||
|
63dc500ae0 | ||
|
723768c828 | ||
|
e7bda080b4 | ||
|
8fd677f4ae | ||
|
88f646cf12 | ||
|
49d105fd70 | ||
|
ff49c9f731 | ||
|
c628f99928 | ||
|
f26808d22c | ||
|
a1b6bca581 | ||
|
ec091fb747 | ||
|
887f4bbe15 | ||
|
212c56fde5 | ||
|
00e716d84d | ||
|
f0c96008bc | ||
|
343fd36671 | ||
|
a4a7473abb | ||
|
4068668de9 | ||
|
7c4591c550 | ||
|
0718fdc829 | ||
|
7eca527160 | ||
|
f1c54d5d55 | ||
|
1ed7bdcddf | ||
|
8486c0f8ca | ||
|
249133204e | ||
|
c8af9f9055 |
10
.github/prtester.py
vendored
10
.github/prtester.py
vendored
@@ -52,10 +52,14 @@ def testBridges(bridges,status):
|
||||
for listing in lists:
|
||||
selectionvalue = ''
|
||||
listname = listing.get('name')
|
||||
if 'optgroup' in listing.contents[0].name:
|
||||
listing = list(itertools.chain.from_iterable(listing))
|
||||
cleanlist = []
|
||||
for option in listing.contents:
|
||||
if 'optgroup' in option.name:
|
||||
cleanlist.extend(option)
|
||||
else:
|
||||
cleanlist.append(option)
|
||||
firstselectionentry = 1
|
||||
for selectionentry in listing:
|
||||
for selectionentry in cleanlist:
|
||||
if firstselectionentry:
|
||||
selectionvalue = selectionentry.get('value')
|
||||
firstselectionentry = 0
|
||||
|
35
README.md
35
README.md
@@ -40,20 +40,26 @@ Check out RSS-Bridge right now on https://rss-bridge.org/bridge01 or find anothe
|
||||
|
||||
RSS-Bridge requires php 7.4 (or higher).
|
||||
|
||||
### Install with git:
|
||||
### Install with composer or git
|
||||
|
||||
```bash
|
||||
```shell
|
||||
cd /var/www
|
||||
composer create-project --no-dev rss-bridge/rss-bridge
|
||||
```
|
||||
|
||||
```shell
|
||||
cd /var/www
|
||||
git clone https://github.com/RSS-Bridge/rss-bridge.git
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
```shell
|
||||
# Give the http user write permission to the cache folder
|
||||
chown www-data:www-data /var/www/rss-bridge/cache
|
||||
|
||||
# Optionally copy over the default config file
|
||||
cp config.default.ini.php config.ini.php
|
||||
|
||||
# Optionally copy over the default whitelist file
|
||||
cp whitelist.default.txt whitelist.txt
|
||||
```
|
||||
|
||||
Example config for nginx:
|
||||
@@ -169,23 +175,18 @@ Learn more in [bridge api](https://rss-bridge.github.io/rss-bridge/Bridge_API/in
|
||||
|
||||
### How to enable all bridges
|
||||
|
||||
Write an asterisks to `whitelist.txt`:
|
||||
enabled_bridges[] = *
|
||||
|
||||
echo '*' > whitelist.txt
|
||||
### How to enable some bridges
|
||||
|
||||
Learn more in [enabling briges](https://rss-bridge.github.io/rss-bridge/For_Hosts/Whitelisting.html)
|
||||
|
||||
### How to enable a bridge
|
||||
|
||||
Add the bridge name to `whitelist.txt`:
|
||||
|
||||
echo 'FirefoxAddonsBridge' >> whitelist.txt
|
||||
```
|
||||
enabled_bridges[] = TwitchBridge
|
||||
enabled_bridges[] = GettrBridge
|
||||
```
|
||||
|
||||
### How to enable debug mode
|
||||
|
||||
Create a file named `DEBUG`:
|
||||
|
||||
touch DEBUG
|
||||
enable_debug_mode = true
|
||||
|
||||
Learn more in [debug mode](https://rss-bridge.github.io/rss-bridge/For_Developers/Debug_mode.html).
|
||||
|
||||
|
@@ -41,18 +41,14 @@ class ConnectivityAction implements ActionInterface
|
||||
return render_template('connectivity.html.php');
|
||||
}
|
||||
|
||||
$bridgeClassName = $this->bridgeFactory->sanitizeBridgeName($request['bridge']);
|
||||
|
||||
if ($bridgeClassName === null) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
$bridgeClassName = $this->bridgeFactory->createBridgeClassName($request['bridge']);
|
||||
|
||||
return $this->reportBridgeConnectivity($bridgeClassName);
|
||||
}
|
||||
|
||||
private function reportBridgeConnectivity($bridgeClassName)
|
||||
{
|
||||
if (!$this->bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
throw new \Exception('Bridge is not whitelisted!');
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,7 @@ class DetectAction implements ActionInterface
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -16,22 +16,19 @@ class DisplayAction implements ActionInterface
|
||||
{
|
||||
public function execute(array $request)
|
||||
{
|
||||
if (Configuration::getConfig('system', 'enable_maintenance_mode')) {
|
||||
return new Response('503 Service Unavailable', 503);
|
||||
}
|
||||
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
$bridgeClassName = null;
|
||||
if (isset($request['bridge'])) {
|
||||
$bridgeClassName = $bridgeFactory->sanitizeBridgeName($request['bridge']);
|
||||
}
|
||||
|
||||
if ($bridgeClassName === null) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');
|
||||
|
||||
$format = $request['format'] ?? null;
|
||||
if (!$format) {
|
||||
throw new \Exception('You must specify a format!');
|
||||
}
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
throw new \Exception('This bridge is not whitelisted');
|
||||
}
|
||||
|
||||
@@ -41,23 +38,22 @@ class DisplayAction implements ActionInterface
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
$bridge->loadConfiguration();
|
||||
|
||||
$noproxy = array_key_exists('_noproxy', $request)
|
||||
&& filter_var($request['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (Configuration::getConfig('proxy', 'url') && Configuration::getConfig('proxy', 'by_bridge') && $noproxy) {
|
||||
$noproxy = $request['_noproxy'] ?? null;
|
||||
if (
|
||||
Configuration::getConfig('proxy', 'url')
|
||||
&& Configuration::getConfig('proxy', 'by_bridge')
|
||||
&& $noproxy
|
||||
) {
|
||||
// This const is only used once in getContents()
|
||||
define('NOPROXY', true);
|
||||
}
|
||||
|
||||
if (array_key_exists('_cache_timeout', $request)) {
|
||||
if (! Configuration::getConfig('cache', 'custom_timeout')) {
|
||||
unset($request['_cache_timeout']);
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($request);
|
||||
return new Response('', 301, ['Location' => $uri]);
|
||||
}
|
||||
|
||||
$cache_timeout = filter_var($request['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||
$cacheTimeout = $request['_cache_timeout'] ?? null;
|
||||
if (Configuration::getConfig('cache', 'custom_timeout') && $cacheTimeout) {
|
||||
$cacheTimeout = (int) $cacheTimeout;
|
||||
} else {
|
||||
$cache_timeout = $bridge->getCacheTimeout();
|
||||
// At this point the query argument might still be in the url but it won't be used
|
||||
$cacheTimeout = $bridge->getCacheTimeout();
|
||||
}
|
||||
|
||||
// Remove parameters that don't concern bridges
|
||||
@@ -91,23 +87,22 @@ class DisplayAction implements ActionInterface
|
||||
)
|
||||
);
|
||||
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setKey($cache_params);
|
||||
// This cache purge will basically delete all cache items older than 24h, regardless of scope and key
|
||||
$cache->purgeCache(86400);
|
||||
|
||||
$items = [];
|
||||
$infos = [];
|
||||
$mtime = $cache->getTime();
|
||||
|
||||
if (
|
||||
$mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
$mtime
|
||||
&& (time() - $cacheTimeout < $mtime)
|
||||
&& !Debug::isEnabled()
|
||||
) {
|
||||
// At this point we found the feed in the cache
|
||||
// At this point we found the feed in the cache and debug mode is disabled
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
// The client wants to know if the feed has changed since its last check
|
||||
@@ -118,7 +113,7 @@ class DisplayAction implements ActionInterface
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the cached feed from the cache and prepare it
|
||||
// Load the feed from cache and prepare it
|
||||
$cached = $cache->loadData();
|
||||
if (isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
foreach ($cached['items'] as $item) {
|
||||
@@ -127,7 +122,7 @@ class DisplayAction implements ActionInterface
|
||||
$infos = $cached['extraInfos'];
|
||||
}
|
||||
} else {
|
||||
// At this point we did NOT find the feed in the cache. So invoke the bridge!
|
||||
// At this point we did NOT find the feed in the cache or debug mode is enabled.
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->collectData();
|
||||
@@ -169,6 +164,9 @@ class DisplayAction implements ActionInterface
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately need to set scope and key again because they might be modified
|
||||
$cache->setScope('');
|
||||
$cache->setKey($cache_params);
|
||||
$cache->saveData([
|
||||
'items' => array_map(function (FeedItem $item) {
|
||||
return $item->toArray();
|
||||
@@ -200,7 +198,7 @@ class DisplayAction implements ActionInterface
|
||||
$item->setURI(get_current_url());
|
||||
$item->setTimestamp(time());
|
||||
|
||||
// Create a item identifier for feed readers e.g. "staysafetv twitch videos_19389"
|
||||
// Create an item identifier for feed readers e.g. "staysafetv twitch videos_19389"
|
||||
$item->setUid($bridge->getName() . '_' . $uniqueIdentifier);
|
||||
|
||||
$content = render_template(__DIR__ . '/../templates/bridge-error.html.php', [
|
||||
@@ -215,11 +213,10 @@ class DisplayAction implements ActionInterface
|
||||
|
||||
private static function logBridgeError($bridgeName, $code)
|
||||
{
|
||||
$cacheFactory = new CacheFactory();
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('error_reporting');
|
||||
$cache->setkey([$bridgeName . '_' . $code]);
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
|
||||
if ($report = $cache->loadData()) {
|
||||
$report = Json::decode($report);
|
||||
$report['time'] = time();
|
||||
|
@@ -15,7 +15,7 @@ final class FrontpageAction implements ActionInterface
|
||||
|
||||
$body = '';
|
||||
foreach ($bridgeClassNames as $bridgeClassName) {
|
||||
if ($bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
if ($bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
$body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats);
|
||||
$activeBridges++;
|
||||
} elseif ($showInactive) {
|
||||
@@ -24,6 +24,7 @@ final class FrontpageAction implements ActionInterface
|
||||
}
|
||||
|
||||
return render(__DIR__ . '/../templates/frontpage.html.php', [
|
||||
'messages' => [],
|
||||
'admin_email' => Configuration::getConfig('admin', 'email'),
|
||||
'admin_telegram' => Configuration::getConfig('admin', 'telegram'),
|
||||
'bridges' => $body,
|
||||
|
15
actions/HealthAction.php
Normal file
15
actions/HealthAction.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class HealthAction implements ActionInterface
|
||||
{
|
||||
public function execute(array $request)
|
||||
{
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'message' => 'all is good',
|
||||
];
|
||||
return new Response(Json::encode($response), 200, ['content-type' => 'application/json']);
|
||||
}
|
||||
}
|
@@ -26,7 +26,7 @@ class ListAction implements ActionInterface
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
|
||||
$list->bridges[$bridgeClassName] = [
|
||||
'status' => $bridgeFactory->isWhitelisted($bridgeClassName) ? 'active' : 'inactive',
|
||||
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'name' => $bridge->getName(),
|
||||
|
@@ -21,19 +21,12 @@ class SetBridgeCacheAction implements ActionInterface
|
||||
|
||||
$key = $request['key'] or returnClientError('You must specify key!');
|
||||
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
$bridgeClassName = null;
|
||||
if (isset($request['bridge'])) {
|
||||
$bridgeClassName = $bridgeFactory->sanitizeBridgeName($request['bridge']);
|
||||
}
|
||||
|
||||
if ($bridgeClassName === null) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');
|
||||
|
||||
// whitelist control
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
@@ -42,10 +35,12 @@ class SetBridgeCacheAction implements ActionInterface
|
||||
$bridge->loadConfiguration();
|
||||
$value = $request['value'];
|
||||
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope(get_class($bridge));
|
||||
if (!is_array($key)) {
|
||||
// not sure if $key is an array when it comes in from request
|
||||
$key = [$key];
|
||||
}
|
||||
$cache->setKey($key);
|
||||
$cache->saveData($value);
|
||||
|
||||
|
116
bridges/ABolaBridge.php
Normal file
116
bridges/ABolaBridge.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
class ABolaBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'A Bola';
|
||||
const URI = 'https://abola.pt/';
|
||||
const DESCRIPTION = 'Returns news from the Portuguese sports newspaper A BOLA.PT';
|
||||
const MAINTAINER = 'rmscoelho';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'feed' => [
|
||||
'name' => 'News Feed',
|
||||
'type' => 'list',
|
||||
'title' => 'Feeds from the Portuguese sports newspaper A BOLA.PT',
|
||||
'values' => [
|
||||
'Últimas' => 'Nnh/Noticias',
|
||||
'Seleção Nacional' => 'Selecao/Noticias',
|
||||
'Futebol Nacional' => [
|
||||
'Notícias' => 'Nacional/Noticias',
|
||||
'Primeira Liga' => 'Nacional/Liga/Noticias',
|
||||
'Liga 2' => 'Nacional/Liga2/Noticias',
|
||||
'Liga 3' => 'Nacional/Liga3/Noticias',
|
||||
'Liga Revelação' => 'Nacional/Liga-Revelacao/Noticias',
|
||||
'Campeonato de Portugal' => 'Nacional/Campeonato-Portugal/Noticias',
|
||||
'Distritais' => 'Nacional/Distritais/Noticias',
|
||||
'Taça de Portugal' => 'Nacional/TPortugal/Noticias',
|
||||
'Futebol Feminino' => 'Nacional/FFeminino/Noticias',
|
||||
'Futsal' => 'Nacional/Futsal/Noticias',
|
||||
],
|
||||
'Futebol Internacional' => [
|
||||
'Notícias' => 'Internacional/Noticias/Noticias',
|
||||
'Liga dos Campeões' => 'Internacional/Liga-dos-campeoes/Noticias',
|
||||
'Liga Europa' => 'Internacional/Liga-europa/Noticias',
|
||||
'Liga Conferência' => 'Internacional/Liga-conferencia/Noticias',
|
||||
'Liga das Nações' => 'Internacional/Liga-das-nacoes/Noticias',
|
||||
'UEFA Youth League' => 'Internacional/Uefa-Youth-League/Noticias',
|
||||
],
|
||||
'Mercado' => 'Mercado',
|
||||
'Modalidades' => 'Modalidades/Noticias',
|
||||
'Motores' => 'Motores/Noticias',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://abola.pt/img/icons/favicon-96x96.png';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return !is_null($this->getKey('feed')) ? self::NAME . ' | ' . $this->getKey('feed') : self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return self::URI . $this->getInput('feed');
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = sprintf('https://abola.pt/%s', $this->getInput('feed'));
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
if ($this->getInput('feed') !== 'Mercado') {
|
||||
$dom = $dom->find('div#body_Todas1_upNoticiasTodas', 0);
|
||||
} else {
|
||||
$dom = $dom->find('div#body_NoticiasMercado_upNoticiasTodas', 0);
|
||||
}
|
||||
if (!$dom) {
|
||||
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
|
||||
}
|
||||
$dom = defaultLinkTo($dom, $this->getURI());
|
||||
foreach ($dom->find('div.media') as $key => $article) {
|
||||
//Get thumbnail
|
||||
$image = $article->find('.media-img', 0)->style;
|
||||
$image = preg_replace('/background-image: url\(/i', '', $image);
|
||||
$image = substr_replace($image, '', -4);
|
||||
$image = preg_replace('/https:\/\//i', '', $image);
|
||||
$image = preg_replace('/www\./i', '', $image);
|
||||
$image = preg_replace('/\/\//', '/', $image);
|
||||
$image = preg_replace('/\/\/\//', '//', $image);
|
||||
$image = substr($image, 7);
|
||||
$image = 'https://' . $image;
|
||||
$image = preg_replace('/ptimg/', 'pt/img', $image);
|
||||
$image = preg_replace('/\/\/bola/', 'www.abola', $image);
|
||||
//Timestamp
|
||||
$date = date('Y/m/d');
|
||||
if (!is_null($article->find("span#body_Todas1_rptNoticiasTodas_lblData_$key", 0))) {
|
||||
$date = $article->find("span#body_Todas1_rptNoticiasTodas_lblData_$key", 0)->plaintext;
|
||||
$date = preg_replace('/\./', '/', $date);
|
||||
}
|
||||
$time = $article->find("span#body_Todas1_rptNoticiasTodas_lblHora_$key", 0)->plaintext;
|
||||
$date = explode('/', $date);
|
||||
$time = explode(':', $time);
|
||||
$year = $date[0];
|
||||
$month = $date[1];
|
||||
$day = $date[2];
|
||||
$hour = $time[0];
|
||||
$minute = $time[1];
|
||||
$timestamp = mktime($hour, $minute, 0, $month, $day, $year);
|
||||
//Content
|
||||
$image = '<img src="' . $image . '" alt="' . $article->find('h4 span', 0)->plaintext . '" />';
|
||||
$description = '<p>' . $article->find('.media-texto > span', 0)->plaintext . '</p>';
|
||||
$content = $image . '</br>' . $description;
|
||||
$a = $article->find('.media-body > a', 0);
|
||||
$this->items[] = [
|
||||
'title' => $a->find('h4 span', 0)->plaintext,
|
||||
'uri' => $a->href,
|
||||
'content' => $content,
|
||||
'timestamp' => $timestamp,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,7 +34,12 @@ class ASRockNewsBridge extends BridgeAbstract
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['enclosures'][] = $a->find('img', 0)->src;
|
||||
|
||||
$img = $a->find('img', 0);
|
||||
if ($img) {
|
||||
$item['enclosures'][] = $img->src;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
|
85
bridges/AllSidesBridge.php
Normal file
85
bridges/AllSidesBridge.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
class AllSidesBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'AllSides';
|
||||
const URI = 'https://www.allsides.com';
|
||||
const DESCRIPTION = 'Balanced news and media bias ratings.';
|
||||
const MAINTAINER = 'Oliver Nutter';
|
||||
const PARAMETERS = [
|
||||
'global' => [
|
||||
'limit' => [
|
||||
'name' => 'Number of posts to return',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'required' => false,
|
||||
'title' => 'Zero or negative values return all posts (ignored if not fetching full article)',
|
||||
],
|
||||
'fetch' => [
|
||||
'name' => 'Fetch full article content',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked',
|
||||
],
|
||||
],
|
||||
'Headline Roundups' => [],
|
||||
];
|
||||
|
||||
private const ROUNDUPS_URI = self::URI . '/headline-roundups';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Headline Roundups':
|
||||
$index = getSimpleHTMLDOM(self::ROUNDUPS_URI);
|
||||
defaultLinkTo($index, self::ROUNDUPS_URI);
|
||||
$entries = $index->find('table.views-table > tbody > tr');
|
||||
|
||||
$limit = (int) $this->getInput('limit');
|
||||
$fetch = (bool) $this->getInput('fetch');
|
||||
|
||||
if ($limit > 0 && $fetch) {
|
||||
$entries = array_slice($entries, 0, $limit);
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$item = [
|
||||
'title' => $entry->find('.views-field-name', 0)->text(),
|
||||
'uri' => $entry->find('a', 0)->href,
|
||||
'timestamp' => $entry->find('.date-display-single', 0)->content,
|
||||
'author' => 'AllSides Staff',
|
||||
];
|
||||
|
||||
if ($fetch) {
|
||||
$article = getSimpleHTMLDOMCached($item['uri']);
|
||||
defaultLinkTo($article, $item['uri']);
|
||||
|
||||
$item['content'] = $article->find('.story-id-page-description', 0);
|
||||
|
||||
foreach ($article->find('.page-tags a') as $tag) {
|
||||
$item['categories'][] = $tag->text();
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->queriedContext) {
|
||||
return self::NAME . " - {$this->queriedContext}";
|
||||
}
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Headline Roundups':
|
||||
return self::ROUNDUPS_URI;
|
||||
}
|
||||
return self::URI;
|
||||
}
|
||||
}
|
41
bridges/AllocineFRSortiesBridge.php
Normal file
41
bridges/AllocineFRSortiesBridge.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
class AllocineFRSortiesBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Simounet';
|
||||
const NAME = 'AlloCiné Sorties Bridge';
|
||||
const CACHE_TIMEOUT = 25200; // 7h
|
||||
const BASE_URI = 'https://www.allocine.fr';
|
||||
const URI = self::BASE_URI . '/film/sorties-semaine/';
|
||||
const DESCRIPTION = 'Bridge for AlloCiné - Sorties cinéma cette semaine';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
foreach ($html->find('section.section.section-wrap', 0)->find('li.mdl') as $element) {
|
||||
$item = [];
|
||||
|
||||
$thumb = $element->find('figure.thumbnail', 0);
|
||||
$meta = $element->find('div.meta-body', 0);
|
||||
$synopsis = $element->find('div.synopsis', 0);
|
||||
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
$content = trim(defaultLinkTo($thumb->outertext . $meta->outertext . $synopsis->outertext, static::URI));
|
||||
|
||||
// Replace image 'src' with the one in 'data-src'
|
||||
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9=+\/]*"@', '', $content);
|
||||
$content = preg_replace('@data-src=@', 'src=', $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::BASE_URI . '/' . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -85,7 +85,7 @@ class BugzillaBridge extends BridgeAbstract
|
||||
protected function getTitle($url)
|
||||
{
|
||||
// Only request the summary for a faster request
|
||||
$json = json_decode(getContents($url . '?include_fields=summary'), true);
|
||||
$json = self::getJSON($url . '?include_fields=summary');
|
||||
$this->title = 'Bug ' . $this->bugid . ' - ' .
|
||||
$json['bugs'][0]['summary'] . ' - ' .
|
||||
// Remove https://
|
||||
@@ -94,7 +94,7 @@ class BugzillaBridge extends BridgeAbstract
|
||||
|
||||
protected function collectComments($url)
|
||||
{
|
||||
$json = json_decode(getContents($url), true);
|
||||
$json = self::getJSON($url);
|
||||
|
||||
// Array of comments is here
|
||||
if (!isset($json['bugs'][$this->bugid]['comments'])) {
|
||||
@@ -127,7 +127,7 @@ class BugzillaBridge extends BridgeAbstract
|
||||
|
||||
protected function collectUpdates($url)
|
||||
{
|
||||
$json = json_decode(getContents($url), true);
|
||||
$json = self::getJSON($url);
|
||||
|
||||
// Array of changesets which contain an array of changes
|
||||
if (!isset($json['bugs']['0']['history'])) {
|
||||
@@ -159,7 +159,7 @@ class BugzillaBridge extends BridgeAbstract
|
||||
protected function getUser($user)
|
||||
{
|
||||
// Check if the user endpoint is available
|
||||
if ($this->loadCacheValue($this->instance . 'userEndpointClosed')) {
|
||||
if ($this->loadCacheValue($this->instance . 'userEndpointClosed', 86400)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ class BugzillaBridge extends BridgeAbstract
|
||||
|
||||
$url = $this->instance . '/rest/user/' . $user . '?include_fields=real_name';
|
||||
try {
|
||||
$json = json_decode(getContents($url), true);
|
||||
$json = self::getJSON($url);
|
||||
if (isset($json['error']) and $json['error']) {
|
||||
throw new Exception();
|
||||
}
|
||||
@@ -187,4 +187,12 @@ class BugzillaBridge extends BridgeAbstract
|
||||
$this->saveCacheValue($this->instance . $user, $username);
|
||||
return $username;
|
||||
}
|
||||
|
||||
protected static function getJSON($url)
|
||||
{
|
||||
$headers = [
|
||||
'Accept: application/json',
|
||||
];
|
||||
return json_decode(getContents($url, $headers), true);
|
||||
}
|
||||
}
|
||||
|
@@ -55,12 +55,20 @@ class CaschyBridge extends FeedExpander
|
||||
private function addArticleToItem($item, $article)
|
||||
{
|
||||
// remove unwanted stuff
|
||||
foreach ($article->find('div.video-container, div.aawp, p.aawp-disclaimer, iframe.wp-embedded-content, div.wp-embed, p.wp-caption-text') as $element) {
|
||||
foreach (
|
||||
$article->find('div.video-container, div.aawp, p.aawp-disclaimer, iframe.wp-embedded-content,
|
||||
div.wp-embed, p.wp-caption-text, script') as $element
|
||||
) {
|
||||
$element->remove();
|
||||
}
|
||||
// reload html, as remove() is buggy
|
||||
$article = str_get_html($article->outertext);
|
||||
|
||||
$categories = $article->find('div.post-category a');
|
||||
foreach ($categories as $category) {
|
||||
$item['categories'][] = $category->plaintext;
|
||||
}
|
||||
|
||||
$content = $article->find('div.entry-inner', 0);
|
||||
$item['content'] = $content;
|
||||
|
||||
|
75
bridges/CorreioDaFeiraBridge.php
Normal file
75
bridges/CorreioDaFeiraBridge.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
class CorreioDaFeiraBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Correio da Feira';
|
||||
const URI = 'https://www.correiodafeira.pt/';
|
||||
const DESCRIPTION = 'Returns news from the Portuguese local newspaper Correio da Feira';
|
||||
const MAINTAINER = 'rmscoelho';
|
||||
const CACHE_TIMEOUT = 86400;
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'feed' => [
|
||||
'name' => 'News Feed',
|
||||
'type' => 'list',
|
||||
'title' => 'Feeds from the Portuguese sports newspaper A BOLA.PT',
|
||||
'values' => [
|
||||
'Cultura' => 'cultura',
|
||||
'Desporto' => 'desporto',
|
||||
'Economia' => 'economia',
|
||||
'Entrevista' => 'entrevista',
|
||||
'Freguesias' => 'freguesias',
|
||||
'Justiça' => 'justica',
|
||||
'Opinião' => 'opiniao',
|
||||
'Política' => 'politica',
|
||||
'Reportagem' => 'reportagem',
|
||||
'Sociedade' => 'sociedade',
|
||||
'Tecnologia' => 'tecnologia',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.correiodafeira.pt/wp-content/uploads/base_reporter-200x200.jpg';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return !is_null($this->getKey('feed')) ? self::NAME . ' | ' . $this->getKey('feed') : self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return self::URI . $this->getInput('feed');
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = sprintf('https://www.correiodafeira.pt/categoria/%s', $this->getInput('feed'));
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
$dom = $dom->find('main', 0);
|
||||
if (!$dom) {
|
||||
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
|
||||
}
|
||||
$dom = defaultLinkTo($dom, $this->getURI());
|
||||
foreach ($dom->find('div.post') as $article) {
|
||||
$a = $article->find('div.blog-box', 0);
|
||||
//Get date and time of publishing
|
||||
$time = $a->find('.post-date > :nth-child(2)', 0)->plaintext;
|
||||
$datetime = explode('/', $time);
|
||||
$year = $datetime[2];
|
||||
$month = $datetime[1];
|
||||
$day = $datetime[0];
|
||||
$timestamp = mktime(0, 0, 0, $month, $day, $year);
|
||||
$this->items[] = [
|
||||
'title' => $a->find('h2.entry-title > a', 0)->plaintext,
|
||||
'uri' => $a->find('h2.entry-title > a', 0)->href,
|
||||
'author' => $a->find('li.post-author > a', 0)->plaintext,
|
||||
'content' => $a->find('.entry-content > p', 0)->plaintext,
|
||||
'timestamp' => $timestamp,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -63,7 +63,7 @@ class CraigslistBridge extends BridgeAbstract
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
// Check if no results page is shown (nearby results)
|
||||
if ($html->find('.displaycountShow', 0)->plaintext == '0') {
|
||||
if (($html->find('.displaycountShow', 0)->plaintext ?? '') == '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,10 @@ class CuriousCatBridge extends BridgeAbstract
|
||||
|
||||
$apiJson = getContents($url);
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
$apiData = Json::decode($apiJson);
|
||||
if (isset($apiData['error'])) {
|
||||
throw new \Exception($apiData['error_code']);
|
||||
}
|
||||
|
||||
foreach ($apiData['posts'] as $post) {
|
||||
$item = [];
|
||||
|
102
bridges/EBayBridge.php
Normal file
102
bridges/EBayBridge.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
class EBayBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'eBay';
|
||||
const DESCRIPTION = 'Returns the search results from the eBay auctioning platforms';
|
||||
const URI = 'https://www.eBay.com';
|
||||
const MAINTAINER = 'wrobelda';
|
||||
const PARAMETERS = [[
|
||||
'url' => [
|
||||
'name' => 'Search URL',
|
||||
'title' => 'Copy the URL from your browser\'s address bar after searching for your items and paste it here',
|
||||
'pattern' => '^(https:\/\/)?(www.)?ebay\.(com|com\.au|at|be|ca|ch|cn|es|fr|de|com\.hk|ie|it|com\.my|nl|ph|pl|com\.sg|co\.uk).*$',
|
||||
'exampleValue' => 'https://www.ebay.com/sch/i.html?_nkw=atom+rss',
|
||||
'required' => true,
|
||||
]
|
||||
]];
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if ($this->getInput('url')) {
|
||||
# make sure we order by the most recently listed offers
|
||||
$uri = trim(preg_replace('/([?&])_sop=[^&]+(&|$)/', '$1', $this->getInput('url')), '?&/');
|
||||
$uri .= (parse_url($uri, PHP_URL_QUERY) ? '&' : '?') . '_sop=10';
|
||||
|
||||
return $uri;
|
||||
} else {
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$urlQueries = explode('&', parse_url($this->getInput('url'), PHP_URL_QUERY));
|
||||
|
||||
$searchQuery = array_reduce($urlQueries, function ($q, $p) {
|
||||
if (preg_match('/^_nkw=(.+)$/i', $p, $matches)) {
|
||||
$q[] = str_replace('+', ' ', urldecode($matches[1]));
|
||||
}
|
||||
|
||||
return $q;
|
||||
});
|
||||
|
||||
if ($searchQuery) {
|
||||
return $searchQuery[0];
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
// Remove any unsolicited results, e.g. "Results matching fewer words"
|
||||
foreach ($html->find('ul.srp-results > li.srp-river-answer--REWRITE_START ~ li') as $inexactMatches) {
|
||||
$inexactMatches->remove();
|
||||
}
|
||||
|
||||
$results = $html->find('ul.srp-results > li.s-item');
|
||||
foreach ($results as $listing) {
|
||||
$item = [];
|
||||
|
||||
// Remove "NEW LISTING" label, we sort by the newest, so this is redundant
|
||||
foreach ($listing->find('.LIGHT_HIGHLIGHT') as $new_listing_label) {
|
||||
$new_listing_label->remove();
|
||||
}
|
||||
|
||||
$item['title'] = $listing->find('.s-item__title', 0)->plaintext;
|
||||
|
||||
$subtitle = implode('', $listing->find('.s-item__subtitle'));
|
||||
|
||||
$item['uri'] = $listing->find('.s-item__link', 0)->href;
|
||||
|
||||
preg_match('/.*\/itm\/(\d+).*/i', $item['uri'], $matches);
|
||||
$item['uid'] = $matches[1];
|
||||
|
||||
$price = $listing->find('.s-item__details > .s-item__detail > .s-item__price', 0)->plaintext;
|
||||
$shippingFree = $listing->find('.s-item__details > .s-item__detail > .s-item__freeXDays', 0)->plaintext ?? '';
|
||||
$localDelivery = $listing->find('.s-item__details > .s-item__detail > .s-item__localDelivery', 0)->plaintext ?? '';
|
||||
$logisticsCost = $listing->find('.s-item__details > .s-item__detail > .s-item__logisticsCost', 0)->plaintext ?? '';
|
||||
|
||||
$location = $listing->find('.s-item__details > .s-item__detail > .s-item__location', 0)->plaintext ?? '';
|
||||
|
||||
$sellerInfo = $listing->find('.s-item__seller-info-text', 0)->plaintext ?? '';
|
||||
|
||||
$image = $listing->find('.s-item__image-wrapper > img', 0);
|
||||
if ($image) {
|
||||
// Not quite sure why append fragment here
|
||||
$imageUrl = $image->src . '#.image';
|
||||
$item['enclosures'] = [$imageUrl];
|
||||
}
|
||||
|
||||
$item['content'] = <<<CONTENT
|
||||
<p>$sellerInfo $location</p>
|
||||
<p><span style="font-weight:bold">$price</span> $shippingFree $localDelivery $logisticsCost<span></span></p>
|
||||
<p>$subtitle</p>
|
||||
CONTENT;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -48,7 +48,7 @@ class EZTVBridge extends BridgeAbstract
|
||||
public function collectData()
|
||||
{
|
||||
$eztv_uri = $this->getEztvUri();
|
||||
Debug::log($eztv_uri);
|
||||
Logger::debug($eztv_uri);
|
||||
$ids = explode(',', trim($this->getInput('ids')));
|
||||
foreach ($ids as $id) {
|
||||
$data = json_decode(getContents(sprintf('%s/api/get-torrents?imdb_id=%s', $eztv_uri, $id)));
|
||||
|
@@ -113,9 +113,7 @@ class ElloBridge extends BridgeAbstract
|
||||
|
||||
private function getAPIKey()
|
||||
{
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('ElloBridge');
|
||||
$cache->setKey(['key']);
|
||||
$key = $cache->loadData();
|
||||
|
@@ -47,11 +47,11 @@ class EtsyBridge extends BridgeAbstract
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('p.wt-text-gray > span', 2)->plaintext;
|
||||
$item['author'] = $result->find('p.wt-text-gray > span', 2)->plaintext ?? '';
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext
|
||||
. ($result->find('span.currency-symbol', 0)->plaintext ?? '')
|
||||
. ($result->find('span.currency-value', 0)->plaintext ?? '')
|
||||
. '</p><p>'
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
@@ -14,7 +14,7 @@ TEXT;
|
||||
'feed_name' => [
|
||||
'name' => 'Feed name',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'rss-bridge/FeedMerger',
|
||||
'exampleValue' => 'FeedMerge',
|
||||
],
|
||||
'feed_1' => [
|
||||
'name' => 'Feed url',
|
||||
@@ -58,9 +58,29 @@ TEXT;
|
||||
$feeds = array_filter($feeds);
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
// Fetch all items from the feed
|
||||
// todo: consider wrapping this in a try..catch to not let a single feed break the entire bridge?
|
||||
$this->collectExpandableDatas($feed);
|
||||
if (count($feeds) > 1) {
|
||||
// Allow one or more feeds to fail
|
||||
try {
|
||||
$this->collectExpandableDatas($feed);
|
||||
} catch (HttpException $e) {
|
||||
Logger::warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
|
||||
$this->items[] = [
|
||||
'title' => 'RSS-Bridge: ' . $e->getMessage(),
|
||||
// Give current time so it sorts to the top
|
||||
'timestamp' => time(),
|
||||
];
|
||||
continue;
|
||||
} catch (\Exception $e) {
|
||||
if (str_starts_with($e->getMessage(), 'Unable to parse xml')) {
|
||||
// Allow this particular exception from FeedExpander
|
||||
Logger::warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
|
||||
continue;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
$this->collectExpandableDatas($feed);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by timestamp descending
|
||||
@@ -91,6 +111,6 @@ TEXT;
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->getInput('feed_name') ?: 'rss-bridge/FeedMerger';
|
||||
return $this->getInput('feed_name') ?: 'FeedMerge';
|
||||
}
|
||||
}
|
||||
|
132
bridges/FiderBridge.php
Normal file
132
bridges/FiderBridge.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
class FiderBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Fider Bridge';
|
||||
const URI = 'https://fider.io/';
|
||||
const DESCRIPTION = 'Bridge for any Fider instance';
|
||||
const MAINTAINER = 'Oliver Nutter';
|
||||
const PARAMETERS = [
|
||||
'global' => [
|
||||
'instance' => [
|
||||
'name' => 'Instance URL',
|
||||
'required' => true,
|
||||
'example' => 'https://feedback.fider.io',
|
||||
],
|
||||
],
|
||||
'Post' => [
|
||||
'num' => [
|
||||
'name' => 'Post Number',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
],
|
||||
'limit' => [
|
||||
'name' => 'Number of comments to return',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify number of comments to return',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
private $instance;
|
||||
private $posturi;
|
||||
private $title;
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->title ?? parent::getName();
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return $this->posturi ?? parent::getURI();
|
||||
}
|
||||
|
||||
protected function setTitle($title)
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached($this->instance);
|
||||
$name = $html->find('title', 0)->innertext;
|
||||
|
||||
$this->title = "$title - $name";
|
||||
}
|
||||
|
||||
protected function getItem($post, $response = false, $first = false)
|
||||
{
|
||||
$item = [];
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['timestamp'] = $response ? $post->respondedAt : $post->createdAt;
|
||||
$item['author'] = $post->user->name;
|
||||
|
||||
$datetime = new DateTime($item['timestamp']);
|
||||
if ($response) {
|
||||
$item['uid'] = 'response';
|
||||
$item['content'] = $post->text;
|
||||
$item['title'] = "{$item['author']} marked as $post->status {$datetime->format('M d, Y')}";
|
||||
} elseif ($first) {
|
||||
$item['uid'] = 'post';
|
||||
$item['content'] = $post->description;
|
||||
$item['title'] = $post->title;
|
||||
} else {
|
||||
$item['uid'] = 'comment';
|
||||
$item['content'] = $post->content;
|
||||
$item['title'] = "{$item['author']} commented {$datetime->format('M d, Y')}";
|
||||
}
|
||||
|
||||
$item['uid'] .= $item['author'] . $item['timestamp'];
|
||||
|
||||
// parse markdown with implicit line breaks
|
||||
$item['content'] = markdownToHtml($item['content'], ['breaksEnabled' => true]);
|
||||
|
||||
if (property_exists($post, 'editedAt')) {
|
||||
$item['title'] .= ' (edited)';
|
||||
}
|
||||
|
||||
if ($first) {
|
||||
$item['categories'] = $post->tags;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// collect first post
|
||||
$this->instance = rtrim($this->getInput('instance'), '/');
|
||||
|
||||
$num = $this->getInput('num');
|
||||
$this->posturi = "$this->instance/posts/$num";
|
||||
|
||||
$post_api_uri = "$this->instance/api/v1/posts/$num";
|
||||
$post = json_decode(getContents($post_api_uri));
|
||||
|
||||
$this->setTitle($post->title);
|
||||
|
||||
$item = $this->getItem($post, false, true);
|
||||
$this->items[] = $item;
|
||||
|
||||
// collect response to first post
|
||||
if (property_exists($post, 'response')) {
|
||||
$response = $post->response;
|
||||
$response->status = $post->status;
|
||||
$this->items[] = $this->getItem($response, true);
|
||||
}
|
||||
|
||||
// collect comments
|
||||
$comment_api_uri = "$post_api_uri/comments";
|
||||
$comments = json_decode(getContents($comment_api_uri));
|
||||
|
||||
foreach ($comments as $post) {
|
||||
$item = $this->getItem($post);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
usort($this->items, function ($a, $b) {
|
||||
return $b['timestamp'] <=> $a['timestamp'];
|
||||
});
|
||||
|
||||
if ($this->getInput('limit') ?? 0 > 0) {
|
||||
$this->items = array_slice($this->items, 0, $this->getInput('limit'));
|
||||
}
|
||||
}
|
||||
}
|
@@ -36,6 +36,11 @@ class FinanzflussBridge extends BridgeAbstract
|
||||
$img->srcset = $baseurl . $src;
|
||||
}
|
||||
|
||||
//remove unwanted stuff
|
||||
foreach ($content->find('div.newsletter-signup') as $element) {
|
||||
$element->remove();
|
||||
}
|
||||
|
||||
//get author
|
||||
$author = $domarticle->find('div.author-name', 0);
|
||||
|
||||
|
@@ -900,10 +900,16 @@ class FurAffinityBridge extends BridgeAbstract
|
||||
$submissionHTML = $this->getFASimpleHTMLDOM($submissionURL, $cache);
|
||||
|
||||
$stats = $submissionHTML->find('.stats-container', 0);
|
||||
$item['timestamp'] = strtotime($stats->find('.popup_date', 0)->title);
|
||||
$item['enclosures'] = [
|
||||
$submissionHTML->find('.actions a[href^=https://d.facdn]', 0)->href
|
||||
];
|
||||
$popupDate = $stats->find('.popup_date', 0);
|
||||
if ($popupDate) {
|
||||
$item['timestamp'] = strtotime($popupDate->title);
|
||||
}
|
||||
|
||||
$var = $submissionHTML->find('.actions a[href^=https://d.facdn]', 0);
|
||||
if ($var) {
|
||||
$item['enclosures'] = [$var->href];
|
||||
}
|
||||
|
||||
foreach ($stats->find('#keywords a') as $keyword) {
|
||||
$item['categories'][] = $keyword->plaintext;
|
||||
}
|
||||
|
@@ -90,7 +90,7 @@ class FuturaSciencesBridge extends FeedExpander
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['uri'] = str_replace('#xtor%3DRSS-8', '', $item['uri']);
|
||||
$article = getSimpleHTMLDOMCached($item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
//$item['content'] = $this->extractArticleContent($article);
|
||||
$author = $this->extractAuthor($article);
|
||||
if (!empty($author)) {
|
||||
$item['author'] = $author;
|
||||
|
96
bridges/GameBananaBridge.php
Normal file
96
bridges/GameBananaBridge.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
class GameBananaBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'GameBanana';
|
||||
const MAINTAINER = 'phantop';
|
||||
const URI = 'https://gamebanana.com/';
|
||||
const DESCRIPTION = 'Returns mods from GameBanana.';
|
||||
const PARAMETERS = [
|
||||
'Game' => [
|
||||
'gid' => [
|
||||
'name' => 'Game ID',
|
||||
'required' => true,
|
||||
// Example: latest mods from Zelda: Tears of the Kingdom
|
||||
'exampleValue' => '7617',
|
||||
],
|
||||
'updates' => [
|
||||
'name' => 'Get updates',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable game updates in feed'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://images.gamebanana.com/static/img/favicon/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = 'https://api.gamebanana.com/Core/List/New?itemtype=Mod&page=1&gameid=' . $this->getInput('gid');
|
||||
if ($this->getInput('updates')) {
|
||||
$url .= '&include_updated=1';
|
||||
}
|
||||
$api_response = getContents($url);
|
||||
$json_list = json_decode($api_response, true); // Get first page mod list
|
||||
|
||||
$url = 'https://api.gamebanana.com/Core/Item/Data?itemtype[]=Game&fields[]=name&itemid[]=' . $this->getInput('gid');
|
||||
$fields = 'name,Owner().name,text,screenshots,Files().aFiles(),date,Url().sProfileUrl(),udate';
|
||||
foreach ($json_list as $element) { // Build api request to minimize API calls
|
||||
$mid = $element[1];
|
||||
$url .= '&itemtype[]=Mod&fields[]=' . $fields . '&itemid[]=' . $mid;
|
||||
}
|
||||
$api_response = getContents($url);
|
||||
$json_list = json_decode($api_response, true);
|
||||
|
||||
$this->title = $json_list[0][0];
|
||||
array_shift($json_list); // Take title from API request and remove from json
|
||||
|
||||
foreach ($json_list as $element) {
|
||||
$item = [];
|
||||
$item['uri'] = $element[6];
|
||||
$item['comments'] = $item['uri'] . '#PostsListModule';
|
||||
$item['title'] = $element[0];
|
||||
$item['author'] = $element[1];
|
||||
|
||||
$item['timestamp'] = $element[5];
|
||||
if ($this->getInput('updates')) {
|
||||
$item['timestamp'] = $element[7];
|
||||
}
|
||||
|
||||
$item['enclosures'] = [];
|
||||
foreach ($element[4] as $file) { // Place mod downloads in enclosures
|
||||
array_push($item['enclosures'], 'https://files.gamebanana.com/mods/' . $file['_sFile']);
|
||||
}
|
||||
|
||||
// Get screenshots from element[3]
|
||||
$img_list = json_decode($element[3], true);
|
||||
$item['content'] = '';
|
||||
foreach ($img_list as $img_element) {
|
||||
$item['content'] .= '<img src="https://images.gamebanana.com/img/ss/mods/' . $img_element['_sFile'] . '"/>';
|
||||
}
|
||||
$item['content'] .= '<br>' . $element[2];
|
||||
|
||||
$item['uid'] = $item['uri'] . $item['title'] . $item['timestamp'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$name = parent::getName();
|
||||
if (isset($this->title)) {
|
||||
$name .= " - $this->title";
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
$uri = parent::getURI() . 'games/' . $this->getInput('gid');
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -20,11 +20,13 @@ class GatesNotesBridge extends BridgeAbstract
|
||||
$apiUrl = self::URI . $api_endpoint . http_build_query($params);
|
||||
|
||||
$rawContent = getContents($apiUrl);
|
||||
$cleanedContent = str_replace('\r\n', '', substr($rawContent, 1, -1));
|
||||
$cleanedContent = str_replace('\"', '"', $cleanedContent);
|
||||
$cleanedContent = str_replace([
|
||||
'<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">',
|
||||
'</string>',
|
||||
], '', $rawContent);
|
||||
|
||||
// The content is actually a json between quotes with \r\n inserted
|
||||
$json = json_decode($cleanedContent);
|
||||
$json = Json::decode($cleanedContent, false);
|
||||
|
||||
foreach ($json as $article) {
|
||||
$item = [];
|
||||
@@ -57,8 +59,10 @@ class GatesNotesBridge extends BridgeAbstract
|
||||
$article_html = defaultLinkTo($article_html, $this->getURI());
|
||||
|
||||
$top_description = '<p>' . $article_html->find('div.article_top_description', 0)->innertext . '</p>';
|
||||
$hero_image = '<img src=' . $article_html->find('img.article_top_DMT_Image', 0)->getAttribute('data-src') . '>';
|
||||
|
||||
$heroImage = $article_html->find('img.article_top_DMT_Image', 0);
|
||||
if ($heroImage) {
|
||||
$hero_image = '<img src=' . $heroImage->getAttribute('data-src') . '>';
|
||||
}
|
||||
$article_body = $article_html->find('div.TGN_Article_ReadTimeSection', 0);
|
||||
|
||||
// Remove the menu bar on some articles (PDF download etc.)
|
||||
|
@@ -603,10 +603,10 @@ class GithubTrendingBridge extends BridgeAbstract
|
||||
$item = [];
|
||||
|
||||
// URI
|
||||
$item['uri'] = self::URI_ITEM . $element->find('h1 a', 0)->href;
|
||||
$item['uri'] = self::URI_ITEM . $element->find('h2 a', 0)->href;
|
||||
|
||||
// Title
|
||||
$item['title'] = str_replace(' ', '', trim(strip_tags($element->find('h1 a', 0)->plaintext)));
|
||||
$item['title'] = str_replace(' ', '', trim(strip_tags($element->find('h2 a', 0)->plaintext)));
|
||||
|
||||
// Description
|
||||
$description = $element->find('p', 0);
|
||||
|
@@ -22,7 +22,7 @@ class GizmodoBridge extends FeedExpander
|
||||
// Get header image
|
||||
$image = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$item['content'] = $html->find('div.js_post-content', 0)->innertext;
|
||||
$item['content'] = $html->find('div.js_post-content', 0)->innertext ?? '';
|
||||
|
||||
// Get categories
|
||||
$categories = explode(',', $html->find('meta[name="keywords"]', 0)->content);
|
||||
|
38
bridges/GoAccessBridge.php
Normal file
38
bridges/GoAccessBridge.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class GoAccessBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Simounet';
|
||||
const NAME = 'GoAccess';
|
||||
const URI_BASE = 'https://goaccess.io';
|
||||
const URI = self::URI_BASE . '/release-notes';
|
||||
const CACHE_TIMEOUT = 21600; //6h
|
||||
const DESCRIPTION = 'GoAccess releases.';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$container = $html->find('.container.content', 0);
|
||||
foreach ($container->find('div') as $element) {
|
||||
$titleEl = $element->find('h2', 0);
|
||||
$dateEl = $titleEl->find('small', 0);
|
||||
$date = trim($dateEl->plaintext);
|
||||
$title = is_object($titleEl) ? str_replace($date, '', $titleEl->plaintext) : '';
|
||||
$linkEl = $titleEl->find('a', 0);
|
||||
$link = is_object($linkEl) ? $linkEl->href : '';
|
||||
$postUrl = self::URI . $link;
|
||||
|
||||
$contentEl = $element->find('.dl-horizontal', 0);
|
||||
$content = '<dl>' . $contentEl->xmltext() . '</dl>';
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $postUrl;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['title'] = $title;
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -88,6 +88,14 @@ class GolemBridge extends FeedExpander
|
||||
$item['author'] = $author->plaintext;
|
||||
}
|
||||
|
||||
$categories = $articlePage->find('ul.tags__list li');
|
||||
foreach ($categories as $category) {
|
||||
$trimmedcategories[] = trim(html_entity_decode($category->plaintext));
|
||||
}
|
||||
if (isset($trimmedcategories)) {
|
||||
$item['categories'] = array_unique($trimmedcategories);
|
||||
}
|
||||
|
||||
$item['content'] .= $this->extractContent($articlePage);
|
||||
|
||||
// next page
|
||||
@@ -106,8 +114,8 @@ class GolemBridge extends FeedExpander
|
||||
|
||||
// delete known bad elements
|
||||
foreach (
|
||||
$article->find('div[id*="adtile"], #job-market, #seminars,
|
||||
div.gbox_affiliate, div.toc, .embedcontent') as $bad
|
||||
$article->find('div[id*="adtile"], #job-market, #seminars, iframe,
|
||||
div.gbox_affiliate, div.toc, .embedcontent, script') as $bad
|
||||
) {
|
||||
$bad->remove();
|
||||
}
|
||||
|
@@ -2,19 +2,101 @@
|
||||
|
||||
class GoogleScholarBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Goolge Scholar';
|
||||
const NAME = 'Google Scholar v2';
|
||||
const URI = 'https://scholar.google.com/';
|
||||
const DESCRIPTION = 'Follow authors of scientific publications.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const DESCRIPTION = 'Search for publications or follow authors on Google Scholar.';
|
||||
const MAINTAINER = 'nicholasmccarthy';
|
||||
const CACHE_TIMEOUT = 86400; // 24h
|
||||
|
||||
const PARAMETERS = [[
|
||||
'userId' => [
|
||||
'name' => 'User ID',
|
||||
'exampleValue' => 'qc6CJjYAAAAJ',
|
||||
'required' => true
|
||||
]
|
||||
]];
|
||||
const PARAMETERS = [
|
||||
'user' => [
|
||||
'userId' => [
|
||||
'name' => 'User ID',
|
||||
'exampleValue' => 'qc6CJjYAAAAJ',
|
||||
'required' => true
|
||||
]
|
||||
],
|
||||
'query' => [
|
||||
'q' => [
|
||||
'name' => 'Search Query',
|
||||
'title' => 'Search Query',
|
||||
'required' => true,
|
||||
'exampleValue' => 'machine learning'
|
||||
],
|
||||
'cites' => [
|
||||
'name' => 'Cites',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'exampleValue' => '1275980731835430123',
|
||||
'title' => 'Parameter defines unique ID for an article to trigger Cited By searches. Usage of cites
|
||||
will bring up a list of citing documents in Google Scholar. Example value: cites=1275980731835430123.
|
||||
Usage of cites and q parameters triggers search within citing articles.'
|
||||
],
|
||||
'language' => [
|
||||
'name' => 'Language',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'exampleValue' => 'en',
|
||||
'title' => 'Parameter defines the language to use for the Google Scholar search. '
|
||||
],
|
||||
'minCitations' => [
|
||||
'name' => 'Minimum Citations',
|
||||
'required' => false,
|
||||
'type' => 'number',
|
||||
'default' => '0',
|
||||
'title' => 'Parameter defines the minimum number of citations in order for the results to be included.'
|
||||
],
|
||||
'sinceYear' => [
|
||||
'name' => 'Since Year',
|
||||
'required' => false,
|
||||
'type' => 'number',
|
||||
'default' => '0',
|
||||
'title' => 'Parameter defines the year from which you want the results to be included.'
|
||||
],
|
||||
'untilYear' => [
|
||||
'name' => 'Until Year',
|
||||
'required' => false,
|
||||
'type' => 'number',
|
||||
'default' => '0',
|
||||
'title' => 'Parameter defines the year until which you want the results to be included.'
|
||||
],
|
||||
'sortBy' => [
|
||||
'name' => 'Sort By Date',
|
||||
'type' => 'checkbox',
|
||||
'default' => false,
|
||||
'title' => 'Parameter defines articles added in the last year, sorted by date. Alternatively sorts
|
||||
by relevance. This overrides Since-Until Year values.',
|
||||
],
|
||||
'includePatents' => [
|
||||
'name' => 'Include Patents',
|
||||
'type' => 'checkbox',
|
||||
'default' => false,
|
||||
'title' => 'Include Patents',
|
||||
],
|
||||
'includeCitations' => [
|
||||
'name' => 'Include Citations',
|
||||
'type' => 'checkbox',
|
||||
'default' => true,
|
||||
'title' => 'Parameter defines whether you would like to include citations or not.',
|
||||
],
|
||||
'reviewArticles' => [
|
||||
'name' => 'Only Review Articles',
|
||||
'type' => 'checkbox',
|
||||
'default' => false,
|
||||
'title' => 'Parameter defines whether you would like to show only review articles or not (these
|
||||
articles consist of topic reviews, or discuss the works or authors you have searched for).',
|
||||
],
|
||||
'numResults' => [
|
||||
'name' => 'Number of Results (max 20)',
|
||||
'required' => false,
|
||||
'type' => 'number',
|
||||
'default' => 10,
|
||||
'exampleValue' => 10,
|
||||
'title' => 'Number of results to return'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
@@ -23,58 +105,138 @@ class GoogleScholarBridge extends BridgeAbstract
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$uri = self::URI . '/citations?hl=en&view_op=list_works&sortby=pubdate&user=' . $this->getInput('userId');
|
||||
switch ($this->queriedContext) {
|
||||
case 'user':
|
||||
$userId = $this->getInput('userId');
|
||||
$uri = self::URI . '/citations?hl=en&view_op=list_works&sortby=pubdate&user=' . $userId;
|
||||
$html = getSimpleHTMLDOM($uri) or returnServerError('Could not fetch Google Scholar data.');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not fetch Google Scholar data.');
|
||||
$publications = $html->find('tr[class="gsc_a_tr"]');
|
||||
|
||||
$publications = $html->find('tr[class="gsc_a_tr"]');
|
||||
foreach ($publications as $publication) {
|
||||
$articleUrl = self::URI . htmlspecialchars_decode($publication->find('a[class="gsc_a_at"]', 0)->href);
|
||||
$articleTitle = $publication->find('a[class="gsc_a_at"]', 0)->plaintext;
|
||||
|
||||
foreach ($publications as $publication) {
|
||||
$articleUrl = self::URI . htmlspecialchars_decode($publication->find('a[class="gsc_a_at"]', 0)->href);
|
||||
$articleTitle = $publication->find('a[class="gsc_a_at"]', 0)->plaintext;
|
||||
# fetch the article itself to extract rest of content
|
||||
$contentArticle = getSimpleHTMLDOMCached($articleUrl);
|
||||
$articleEntries = $contentArticle->find('div[class="gs_scl"]');
|
||||
|
||||
# fetch the article itself to extract rest of content
|
||||
$contentArticle = getSimpleHTMLDOMCached($articleUrl);
|
||||
$articleEntries = $contentArticle->find('div[class="gs_scl"]');
|
||||
$articleDate = '';
|
||||
$articleAbstract = '';
|
||||
$articleAuthor = '';
|
||||
$content = '';
|
||||
|
||||
$articleDate = '';
|
||||
$articleAbstract = '';
|
||||
$articleAuthor = '';
|
||||
$content = '';
|
||||
foreach ($articleEntries as $entry) {
|
||||
$field = $entry->find('div[class="gsc_oci_field"]', 0)->plaintext;
|
||||
$value = $entry->find('div[class="gsc_oci_value"]', 0)->plaintext;
|
||||
|
||||
foreach ($articleEntries as $entry) {
|
||||
$field = $entry->find('div[class="gsc_oci_field"]', 0)->plaintext;
|
||||
$value = $entry->find('div[class="gsc_oci_value"]', 0)->plaintext;
|
||||
if ($field == 'Publication date') {
|
||||
$articleDate = $value;
|
||||
} elseif ($field == 'Description') {
|
||||
$articleAbstract = $value;
|
||||
} elseif ($field == 'Authors') {
|
||||
$articleAuthor = $value;
|
||||
} elseif ($field == 'Scholar articles' || $field == 'Total citations') {
|
||||
continue;
|
||||
} else {
|
||||
$content = $content . $field . ': ' . $value . '<br><br>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($field == 'Publication date') {
|
||||
$articleDate = $value;
|
||||
} else if ($field == 'Description') {
|
||||
$articleAbstract = $value;
|
||||
} else if ($field == 'Authors') {
|
||||
$articleAuthor = $value;
|
||||
} else if ($field == 'Scholar articles' || $field == 'Total citations') {
|
||||
continue;
|
||||
} else {
|
||||
$content = $content . $field . ': ' . $value . '<br><br>';
|
||||
$content = $content . $articleAbstract;
|
||||
|
||||
$item = [];
|
||||
|
||||
$item['title'] = $articleTitle;
|
||||
$item['uri'] = $articleUrl;
|
||||
$item['timestamp'] = strtotime($articleDate);
|
||||
$item['author'] = $articleAuthor;
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'query':
|
||||
$query = urlencode($this->getInput('q'));
|
||||
$cites = $this->getInput('cites');
|
||||
$language = $this->getInput('language');
|
||||
$sinceYear = $this->getInput('sinceYear');
|
||||
$untilYear = $this->getInput('untilYear');
|
||||
$minCitations = (int)$this->getInput('minCitations');
|
||||
$includeCitations = $this->getInput('includeCitations');
|
||||
$includePatents = $this->getInput('includePatents');
|
||||
$reviewArticles = $this->getInput('reviewArticles');
|
||||
$sortBy = $this->getInput('sortBy');
|
||||
$numResults = $this->getInput('numResults');
|
||||
|
||||
# Build URI
|
||||
$uri = self::URI . 'scholar?q=' . $query;
|
||||
$uri .= $sinceYear != 0 ? '&as_ylo=' . $sinceYear : '';
|
||||
$uri .= $untilYear != 0 ? '&as_yhi=' . $untilYear : '';
|
||||
$uri .= $language != '' ? '&hl=' . $language : '';
|
||||
$uri .= $includePatents ? '&as_vis=7' : '&as_vis=0';
|
||||
$uri .= $includeCitations ? '&as_vis=0' : ($includePatents ? '&as_vis=1' : '');
|
||||
$uri .= $reviewArticles ? '&as_rr=1' : '';
|
||||
$uri .= $sortBy ? '&scisbd=1' : '';
|
||||
$uri .= $numResults ? '&num=' . $numResults : '';
|
||||
|
||||
$html = getSimpleHTMLDOM($uri) or returnServerError('Could not fetch Google Scholar data.');
|
||||
|
||||
$publications = $html->find('div[class="gs_r gs_or gs_scl"]');
|
||||
|
||||
foreach ($publications as $publication) {
|
||||
$articleTitleElement = $publication->find('h3[class="gs_rt"]', 0);
|
||||
$articleUrl = $articleTitleElement->find('a', 0)->href;
|
||||
$articleTitle = $articleTitleElement->plaintext;
|
||||
|
||||
$articleDateElement = $publication->find('div[class="gs_a"]', 0);
|
||||
$articleDate = $articleDateElement ? $articleDateElement->plaintext : '';
|
||||
|
||||
$articleAbstractElement = $publication->find('div[class="gs_rs"]', 0);
|
||||
$articleAbstract = $articleAbstractElement ? $articleAbstractElement->plaintext : '';
|
||||
|
||||
$articleAuthorElement = $publication->find('div[class="gs_a"]', 0);
|
||||
$articleAuthor = $articleAuthorElement ? $articleAuthorElement->plaintext : '';
|
||||
|
||||
$bottomRowElement = $publication->find('div[class="gs_fl"]', 0);
|
||||
|
||||
$item = [
|
||||
'title' => $articleTitle,
|
||||
'uri' => $articleUrl,
|
||||
'timestamp' => strtotime($articleDate),
|
||||
'author' => $articleAuthor,
|
||||
'content' => $articleAbstract
|
||||
];
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'user':
|
||||
$this->items[] = $item;
|
||||
break;
|
||||
case 'query':
|
||||
$citedBy = 0;
|
||||
if ($bottomRowElement) {
|
||||
$anchorTags = $bottomRowElement->find('a');
|
||||
foreach ($anchorTags as $anchorTag) {
|
||||
if (strpos($anchorTag->plaintext, 'Cited') !== false) {
|
||||
$parts = explode('Cited by ', $anchorTag->plaintext);
|
||||
if (isset($parts[1])) {
|
||||
$citedBy = (int)$parts[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($citedBy >= $minCitations) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content = $content . $articleAbstract;
|
||||
|
||||
$item = [];
|
||||
|
||||
$item['title'] = $articleTitle;
|
||||
$item['uri'] = $articleUrl;
|
||||
$item['timestamp'] = strtotime($articleDate);
|
||||
$item['author'] = $articleAuthor;
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -118,12 +118,22 @@ class HeiseBridge extends FeedExpander
|
||||
protected function parseItem($feedItem)
|
||||
{
|
||||
$item = parent::parseItem($feedItem);
|
||||
$item['uri'] = explode('?', $item['uri'])[0] . '?seite=all';
|
||||
|
||||
// strip rss parameter
|
||||
$item['uri'] = explode('?', $item['uri'])[0];
|
||||
|
||||
// ignore TechStage articles
|
||||
if (strpos($item['uri'], 'https://www.heise.de') !== 0) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
// abort on heise+ articles and link to archive.ph for full-text content
|
||||
if (str_starts_with($item['title'], 'heise+ |')) {
|
||||
$item['uri'] = 'https://archive.ph/?run=1&url=' . urlencode($item['uri']);
|
||||
return $item;
|
||||
}
|
||||
|
||||
$item['uri'] .= '?seite=all';
|
||||
$article = getSimpleHTMLDOMCached($item['uri']);
|
||||
|
||||
if ($article) {
|
||||
@@ -140,7 +150,7 @@ class HeiseBridge extends FeedExpander
|
||||
$article = defaultLinkTo($article, $item['uri']);
|
||||
|
||||
// remove unwanted stuff
|
||||
foreach ($article->find('figure.branding, a-ad, div.ho-text, a-img, .opt-in__content-container, .a-toc__list') as $element) {
|
||||
foreach ($article->find('figure.branding, a-ad, div.ho-text, a-img, .opt-in__content-container, .a-toc__list, a-collapse') as $element) {
|
||||
$element->remove();
|
||||
}
|
||||
// reload html, as remove() is buggy
|
||||
@@ -151,7 +161,7 @@ class HeiseBridge extends FeedExpander
|
||||
$headerElements = $header->find('p, figure img, noscript img');
|
||||
$item['content'] = implode('', $headerElements);
|
||||
|
||||
$authors = $header->find('.a-creator__names .a-creator__name');
|
||||
$authors = $header->find('.creator__names .creator__name');
|
||||
if ($authors) {
|
||||
$item['author'] = implode(', ', array_map(function ($e) {
|
||||
return $e->plaintext;
|
||||
@@ -159,6 +169,11 @@ class HeiseBridge extends FeedExpander
|
||||
}
|
||||
}
|
||||
|
||||
$categories = $article->find('.article-footer__topics ul.topics li.topics__item');
|
||||
foreach ($categories as $category) {
|
||||
$item['categories'][] = trim($category->plaintext);
|
||||
}
|
||||
|
||||
$content = $article->find('.article-content', 0);
|
||||
if ($content) {
|
||||
$contentElements = $content->find(
|
||||
|
@@ -98,9 +98,7 @@ class InstagramBridge extends BridgeAbstract
|
||||
return $username;
|
||||
}
|
||||
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('InstagramBridge');
|
||||
$cache->setKey([$username]);
|
||||
$key = $cache->loadData();
|
||||
|
@@ -9,23 +9,23 @@ class InternationalInstituteForStrategicStudiesBridge extends BridgeAbstract
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
const DESCRIPTION = 'Returns the latest blog posts from the IISS';
|
||||
|
||||
const TEMPLATE_ID = '{6BCFD2C9-4F0B-4ACE-95D7-D14C8B60CD4D}';
|
||||
const COMPONENT_ID = '{E9850380-3707-43C9-994F-75ECE8048E04}';
|
||||
const TEMPLATE_ID = ['BlogArticlePage', 'BlogPage'];
|
||||
const COMPONENT_ID = '9b0c6919-c78b-4910-9be9-d73e6ee40e50';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = 'https://www.iiss.org/api/filter';
|
||||
$url = 'https://www.iiss.org/api/filteredlist/filter';
|
||||
$opts = [
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'templateId' => [self::TEMPLATE_ID],
|
||||
'templateId' => self::TEMPLATE_ID,
|
||||
'componentId' => self::COMPONENT_ID,
|
||||
'page' => '1',
|
||||
'amount' => 10,
|
||||
'amount' => 1,
|
||||
'filter' => (object)[],
|
||||
'tags' => null,
|
||||
'sortType' => 'DateDesc',
|
||||
'restrictionType' => 'None'
|
||||
'sortType' => 'Newest',
|
||||
'restrictionType' => 'Any'
|
||||
])
|
||||
];
|
||||
$headers = [
|
||||
@@ -36,6 +36,7 @@ class InternationalInstituteForStrategicStudiesBridge extends BridgeAbstract
|
||||
$data = json_decode($json);
|
||||
|
||||
foreach ($data->model->Results as $record) {
|
||||
[$content, $enclosures] = $this->getContents(self::URI . $record->Link);
|
||||
$this->items[] = [
|
||||
'uri' => self::URI . $record->Link,
|
||||
'title' => $record->Heading,
|
||||
@@ -44,7 +45,8 @@ class InternationalInstituteForStrategicStudiesBridge extends BridgeAbstract
|
||||
return $author->Name;
|
||||
}, $record->Authors)),
|
||||
'timestamp' => DateTime::createFromFormat('jS F Y', $record->Date)->format('U'),
|
||||
'content' => $this->getContents(self::URI . $record->Link)
|
||||
'content' => $content,
|
||||
'enclosures' => $enclosures
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -56,6 +58,8 @@ class InternationalInstituteForStrategicStudiesBridge extends BridgeAbstract
|
||||
$scripts = $body->find('script');
|
||||
$result = '';
|
||||
|
||||
$enclosures = [];
|
||||
|
||||
foreach ($scripts as $script) {
|
||||
$script_text = $script->innertext;
|
||||
if (str_contains($script_text, 'ReactDOM.render(React.createElement(Components.Reading')) {
|
||||
@@ -63,14 +67,26 @@ class InternationalInstituteForStrategicStudiesBridge extends BridgeAbstract
|
||||
$result .= $args->Html;
|
||||
} elseif (str_contains($script_text, 'ReactDOM.render(React.createElement(Components.ImagePanel')) {
|
||||
$args = $this->getRenderArguments($script_text);
|
||||
$image_tag = str_replace('src="/-', 'src="' . self::URI . '/-', $args->Image);
|
||||
$result .= '<figure>' . $image_tag . '</figure>';
|
||||
$result .= '<figure><img src="' . self::URI . $args->Image . '"></img></figure>';
|
||||
} elseif (str_contains($script_text, 'ReactDOM.render(React.createElement(Components.Intro')) {
|
||||
$args = $this->getRenderArguments($script_text);
|
||||
$result .= '<p>' . $args->Intro . '</p>';
|
||||
} elseif (str_contains($script_text, 'ReactDOM.render(React.createElement(Components.Footnotes')) {
|
||||
$args = $this->getRenderArguments($script_text);
|
||||
$result .= '<p>' . $args->Content . '</p>';
|
||||
} elseif (str_contains($script_text, 'ReactDOM.render(React.createElement(Components.List')) {
|
||||
$args = $this->getRenderArguments($script_text);
|
||||
foreach ($args->Items as $item) {
|
||||
if ($item->Url != null) {
|
||||
$match = preg_match('/\\"(.*)\\"/', $item->Url, $matches);
|
||||
if ($match > 0) {
|
||||
array_push($enclosures, self::URI . $matches[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
return [$result, $enclosures];
|
||||
}
|
||||
|
||||
private function getRenderArguments($script_text)
|
||||
|
29
bridges/JohannesBlickBridge.php
Normal file
29
bridges/JohannesBlickBridge.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class JohannesBlickBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Johannes Blick';
|
||||
const URI = 'https://www.st-johannes-baptist.de/index.php/unsere-medien/johannesblick-archiv';
|
||||
const DESCRIPTION = 'RSS feed for Johannes Blick';
|
||||
const MAINTAINER = 'jummo4@yahoo.de';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request: ' . self::URI);
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
foreach ($html->find('td > a') as $index => $a) {
|
||||
$item = []; // Create an empty item
|
||||
$articlePath = $a->href;
|
||||
$item['title'] = $a->innertext;
|
||||
$item['uri'] = $articlePath;
|
||||
$item['content'] = '';
|
||||
|
||||
$this->items[] = $item; // Add item to the list
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
class JornalDeNoticiasBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Jornal de Notícias (PT)';
|
||||
const URI = 'https://jn.pt';
|
||||
const DESCRIPTION = 'Jornal de Notícias (JN.PT)';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = [
|
||||
'URL' => [
|
||||
'url' => [
|
||||
'name' => 'URL (relative)',
|
||||
'exampleValue' => 'opiniao/catia-domingues.html',
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://static.globalnoticias.pt/jn/common/images/favicons/favicon-128.png';
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'URL':
|
||||
$url = self::URI . '/' . $this->getInput('url');
|
||||
break;
|
||||
default:
|
||||
$url = self::URI;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$archives = $this->getURI();
|
||||
$html = getSimpleHTMLDOMCached($archives);
|
||||
|
||||
foreach ($html->find('article') as $element) {
|
||||
$item = [];
|
||||
|
||||
$title = $element->find('h2 a', 0);
|
||||
$link = $element->find('h2 a', 0);
|
||||
$auth = $element->find('h3 a', 0);
|
||||
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
$item['author'] = $auth->plaintext;
|
||||
|
||||
$snippet = $element->find('h4 a', 0);
|
||||
if ($snippet) {
|
||||
$item['content'] = $snippet->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
104
bridges/JornalNBridge.php
Normal file
104
bridges/JornalNBridge.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
class JornalNBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Jornal N';
|
||||
const URI = 'https://www.jornaln.pt/';
|
||||
const DESCRIPTION = 'Returns news from the Portuguese local newspaper Jornal N';
|
||||
const MAINTAINER = 'rmscoelho';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'feed' => [
|
||||
'name' => 'News Feed',
|
||||
'type' => 'list',
|
||||
'title' => 'Feeds from the Portuguese sports newspaper A BOLA.PT',
|
||||
'values' => [
|
||||
'Concelhos' => [
|
||||
'Espinho' => 'espinho',
|
||||
'Ovar' => 'ovar',
|
||||
'Santa Maria da Feira' => 'santa-maria-da-feira',
|
||||
],
|
||||
'Cultura' => 'ovar/cultura',
|
||||
'Desporto' => 'desporto',
|
||||
'Economia' => 'santa-maria-da-feira/economia',
|
||||
'Política' => 'santa-maria-da-feira/politica',
|
||||
'Opinião' => 'santa-maria-da-feira/opiniao',
|
||||
'Sociedade' => 'santa-maria-da-feira/sociedade',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
const PT_MONTH_NAMES = [
|
||||
'janeiro' => '01',
|
||||
'fevereiro' => '02',
|
||||
'março' => '03',
|
||||
'abril' => '04',
|
||||
'maio' => '05',
|
||||
'junho' => '06',
|
||||
'julho' => '07',
|
||||
'agosto' => '08',
|
||||
'setembro' => '09',
|
||||
'outubro' => '10',
|
||||
'novembro' => '11',
|
||||
'dezembro' => '12',
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.jornaln.pt/wp-content/uploads/2023/01/cropped-NovoLogoJornal_Instagram-192x192.png';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->getKey('feed')) {
|
||||
return self::NAME . ' | ' . $this->getKey('feed');
|
||||
}
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return self::URI . $this->getInput('feed');
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = sprintf(self::URI . '/%s', $this->getInput('feed'));
|
||||
$dom = getSimpleHTMLDOMCached($url);
|
||||
$domSelector = '.elementor-widget-container > .elementor-posts-container';
|
||||
$dom = $dom->find($domSelector, 0);
|
||||
if (!$dom) {
|
||||
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
|
||||
}
|
||||
$dom = defaultLinkTo($dom, $this->getURI());
|
||||
foreach ($dom->find('article') as $article) {
|
||||
//Get thumbnail
|
||||
$image = $article->find('.elementor-post__thumbnail img', 0)->src;
|
||||
//Timestamp
|
||||
$date = $article->find('.elementor-post-date', 0)->plaintext;
|
||||
$date = trim($date, "\t ");
|
||||
$date = preg_replace('/ de /i', '/', $date);
|
||||
$date = preg_replace('/, /', '/', $date);
|
||||
$date = explode('/', $date);
|
||||
$year = (int) $date[2];
|
||||
$month = (int) $date[1];
|
||||
$day = (int) $date[0];
|
||||
foreach (self::PT_MONTH_NAMES as $key => $item) {
|
||||
if ($key === strtolower($month)) {
|
||||
$month = (int) $item;
|
||||
}
|
||||
}
|
||||
$timestamp = mktime(0, 0, 0, $month, $day, $year);
|
||||
//Content
|
||||
$content = '<img src="' . $image . '" alt="' . $article->find('.elementor-post__title > a', 0)->plaintext . '" />';
|
||||
$this->items[] = [
|
||||
'title' => $article->find('.elementor-post__title > a', 0)->plaintext,
|
||||
'uri' => $article->find('a', 0)->href,
|
||||
'content' => $content,
|
||||
'timestamp' => $timestamp
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -169,10 +169,17 @@ class JustWatchBridge extends BridgeAbstract
|
||||
foreach ($titles as $title) {
|
||||
$item = [];
|
||||
$item['uri'] = $title->find('a', 0)->href;
|
||||
$item['title'] = $provider->find('picture > img', 0)->alt . ' - ' . $title->find('.title-poster__image > img', 0)->alt;
|
||||
$image = $title->find('.title-poster__image > img', 0)->attr['src'];
|
||||
if (str_starts_with($image, 'data')) {
|
||||
$image = $title->find('.title-poster__image > img', 0)->attr['data-src'];
|
||||
|
||||
$itemTitle = sprintf(
|
||||
'%s - %s',
|
||||
$provider->find('picture > img', 0)->alt ?? '',
|
||||
$title->find('.title-poster__image > img', 0)->alt ?? ''
|
||||
);
|
||||
$item['title'] = $itemTitle;
|
||||
|
||||
$imageUrl = $title->find('.title-poster__image > img', 0)->attr['src'] ?? '';
|
||||
if (str_starts_with($imageUrl, 'data')) {
|
||||
$imageUrl = $title->find('.title-poster__image > img', 0)->attr['data-src'];
|
||||
}
|
||||
|
||||
$content = '<b>Provider:</b> '
|
||||
@@ -190,7 +197,7 @@ class JustWatchBridge extends BridgeAbstract
|
||||
$content .= '<b>Poster:</b><br><a href="'
|
||||
. $title->find('a', 0)->href
|
||||
. '"><img src="'
|
||||
. $image
|
||||
. $imageUrl
|
||||
. '"></a>';
|
||||
|
||||
$item['content'] = $content;
|
||||
|
88
bridges/MagellantvBridge.php
Normal file
88
bridges/MagellantvBridge.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
class MagellantvBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Magellantv articles';
|
||||
const URI = 'https://www.magellantv.com/articles';
|
||||
const DESCRIPTION = 'Articles of the documentery streaming service Magellantv';
|
||||
const MAINTAINER = 'Vincentvd';
|
||||
const CACHE_TIMEOUT = 60; // 15 minutes
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'topic' => [
|
||||
'type' => 'list',
|
||||
'name' => 'Article topic',
|
||||
'values' => [
|
||||
'All topics' => 'all',
|
||||
'Ancient history' => 'ancient-history',
|
||||
'Art & culture' => 'art-culture',
|
||||
'Biography' => 'biography',
|
||||
'Current history' => 'current-history',
|
||||
'Early modern' => 'early-modern',
|
||||
'Earth' => 'earth',
|
||||
'Mind & body' => 'mind-body',
|
||||
'Nature' => 'nature',
|
||||
'Science & tech' => 'science-tech',
|
||||
'Short takes' => 'short-takes',
|
||||
'Space' => 'space',
|
||||
'Travel & adventure' => 'travel-adventure',
|
||||
'True crime' => 'true-crime',
|
||||
'War & military' => 'war-military'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.magellantv.com/favicon-32x32.png';
|
||||
}
|
||||
|
||||
private function retrieveTags($article)
|
||||
{
|
||||
// Retrieve all tags from an article and store in array
|
||||
$article_tags_list = $article->find('div.articleCategory_article-category-tag__uEAXz > a');
|
||||
$tags = [];
|
||||
foreach ($article_tags_list as $tag) {
|
||||
array_push($tags, $tag->plaintext);
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Determine URL based on topic
|
||||
$topic = $this->getInput('topic');
|
||||
if ($topic == 'all') {
|
||||
$url = 'https://www.magellantv.com/articles';
|
||||
} else {
|
||||
$url = sprintf('https://www.magellantv.com/articles/category/%s', $topic);
|
||||
}
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
|
||||
// Check whether items exists
|
||||
$article_list = $dom->find('div.articlePreview_preview-card__mLMOm');
|
||||
if (sizeof($article_list) == 0) {
|
||||
throw new Exception(sprintf('Unable to find css selector on `%s`', $url));
|
||||
}
|
||||
|
||||
// Loop over each article and store article information
|
||||
foreach ($article_list as $article) {
|
||||
$article = defaultLinkTo($article, $this->getURI());
|
||||
$meta_information = $article->find('div.articlePreview_article-metas__kD1i7', 0);
|
||||
$title = $article->find('div.articlePreview_article-title___Ci5V > h2 > a', 0);
|
||||
$tags_list = $this->retrieveTags($article);
|
||||
|
||||
$item = [
|
||||
'title' => $title->plaintext,
|
||||
'uri' => $title->href,
|
||||
'timestamp' => strtotime($meta_information->find('div.articlePreview_article-date__8Jyfn', 0)->plaintext),
|
||||
'author' => $meta_information->find('div.articlePreview_article-author__Ie0_u > span', 1)->plaintext,
|
||||
'categories' => $tags_list
|
||||
];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,6 +21,18 @@ class MangaDexBridge extends BridgeAbstract
|
||||
'exampleValue' => 'en,jp',
|
||||
'required' => false
|
||||
],
|
||||
'images' => [
|
||||
'name' => 'Fetch chapter page images',
|
||||
'type' => 'list',
|
||||
'title' => 'Places chapter images in feed contents. Entries will consume more bandwidth.',
|
||||
'defaultValue' => 'no',
|
||||
'values' => [
|
||||
'None' => 'no',
|
||||
'Data Saver' => 'saver',
|
||||
'Full Quality' => 'yes'
|
||||
]
|
||||
]
|
||||
|
||||
],
|
||||
'Title Chapters' => [
|
||||
'url' => [
|
||||
@@ -239,6 +251,27 @@ class MangaDexBridge extends BridgeAbstract
|
||||
$item['content'] .= '<br>Other Users: ' . implode(', ', $users);
|
||||
}
|
||||
|
||||
// Fetch chapter page images if desired and add to content
|
||||
if ($this->getInput('images') !== 'no') {
|
||||
$api_uri = self::API_ROOT . 'at-home/server/' . $item['uid'];
|
||||
$header = [ 'Content-Type: application/json' ];
|
||||
$pages = json_decode(getContents($api_uri, $header), true);
|
||||
if ($pages['result'] != 'ok') {
|
||||
returnServerError('Could not retrieve API results');
|
||||
}
|
||||
|
||||
if ($this->getInput('images') == 'saver') {
|
||||
$page_base = $pages['baseUrl'] . '/data-saver/' . $pages['chapter']['hash'] . '/';
|
||||
foreach ($pages['chapter']['dataSaver'] as $image) {
|
||||
$item['content'] .= '<br><img src="' . $page_base . $image . '"/>';
|
||||
}
|
||||
} else {
|
||||
$page_base = $pages['baseUrl'] . '/data/' . $pages['chapter']['hash'] . '/';
|
||||
foreach ($pages['chapter']['data'] as $image) {
|
||||
$item['content'] .= '<br><img src="' . $page_base . $image . '"/>';
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -319,7 +319,7 @@ EOD;
|
||||
$content .= $module['note'];
|
||||
break;
|
||||
case 'listicle':
|
||||
$content .= '<h2>' . $module['title'] . '</h2>';
|
||||
$content .= '<h2>' . ($module['title'] ?? '(no title)') . '</h2>';
|
||||
if (isset($module['image'])) {
|
||||
$content .= $this->handleImages($module['image'], $module['image']['cmsType']);
|
||||
}
|
||||
|
@@ -19,8 +19,10 @@ class NotAlwaysBridge extends BridgeAbstract
|
||||
'Romantic' => 'romantic',
|
||||
'Related' => 'related',
|
||||
'Learning' => 'learning',
|
||||
'Friendly' => 'friendly',
|
||||
'Hopeless' => 'hopeless',
|
||||
'Healthy' => 'healthy',
|
||||
'Legal' => 'legal',
|
||||
'Friendly' => 'friendly',
|
||||
'Unfiltered' => 'unfiltered'
|
||||
]
|
||||
]
|
||||
@@ -38,7 +40,9 @@ class NotAlwaysBridge extends BridgeAbstract
|
||||
#print_r($post);
|
||||
$item = [];
|
||||
$item['uri'] = $post->find('h1', 0)->find('a', 0)->href;
|
||||
$item['content'] = $post;
|
||||
$postHeader = $post->find('.post_header', 0);
|
||||
$storyContent = $post->find('.storycontent', 0);
|
||||
$item['content'] = $postHeader . '<br/><br/>' . $storyContent;
|
||||
$item['title'] = $post->find('h1', 0)->find('a', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -2,10 +2,24 @@
|
||||
|
||||
class NyaaTorrentsBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'ORelio';
|
||||
const MAINTAINER = 'ORelio & Jisagi';
|
||||
const NAME = 'NyaaTorrents';
|
||||
const URI = 'https://nyaa.si/';
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const MAX_ITEMS = 20;
|
||||
const CUSTOM_FIELD_PREFIX = 'nyaa:';
|
||||
const CUSTOM_FIELDS = [
|
||||
self::CUSTOM_FIELD_PREFIX . 'seeders' => 'seeders',
|
||||
self::CUSTOM_FIELD_PREFIX . 'leechers' => 'leechers',
|
||||
self::CUSTOM_FIELD_PREFIX . 'downloads' => 'downloads',
|
||||
self::CUSTOM_FIELD_PREFIX . 'infoHash' => 'infoHash',
|
||||
self::CUSTOM_FIELD_PREFIX . 'categoryId' => 'categoryId',
|
||||
self::CUSTOM_FIELD_PREFIX . 'category' => 'category',
|
||||
self::CUSTOM_FIELD_PREFIX . 'size' => 'size',
|
||||
self::CUSTOM_FIELD_PREFIX . 'comments' => 'comments',
|
||||
self::CUSTOM_FIELD_PREFIX . 'trusted' => 'trusted',
|
||||
self::CUSTOM_FIELD_PREFIX . 'remake' => 'remake'
|
||||
];
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'f' => [
|
||||
@@ -65,23 +79,41 @@ class NyaaTorrentsBridge extends FeedExpander
|
||||
return self::URI . 'static/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
public function getURI()
|
||||
{
|
||||
$this->collectExpandableDatas(
|
||||
self::URI . '?page=rss&s=id&o=desc&'
|
||||
return self::URI . '?page=rss&s=id&o=desc&'
|
||||
. http_build_query([
|
||||
'f' => $this->getInput('f'),
|
||||
'c' => $this->getInput('c'),
|
||||
'q' => $this->getInput('q'),
|
||||
'u' => $this->getInput('u')
|
||||
]),
|
||||
20
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$content = getContents($this->getURI());
|
||||
$content = $this->fixCustomFields($content);
|
||||
$rssContent = simplexml_load_string(trim($content));
|
||||
$this->collectRss2($rssContent, self::MAX_ITEMS);
|
||||
}
|
||||
|
||||
private function fixCustomFields($content)
|
||||
{
|
||||
$broken = array_keys(self::CUSTOM_FIELDS);
|
||||
$fixed = array_values(self::CUSTOM_FIELDS);
|
||||
return str_replace($broken, $fixed, $content);
|
||||
}
|
||||
|
||||
protected function parseItem($newItem)
|
||||
{
|
||||
$item = parent::parseItem($newItem);
|
||||
$item = parent::parseRss2Item($newItem);
|
||||
|
||||
// Add nyaa custom fields
|
||||
$item['id'] = str_replace(['https://nyaa.si/download/', '.torrent'], '', $item['uri']);
|
||||
foreach (array_values(self::CUSTOM_FIELDS) as $value) {
|
||||
$item[$value] = (string) $newItem->$value;
|
||||
}
|
||||
|
||||
//Convert URI from torrent file to web page
|
||||
$item['uri'] = str_replace('/download/', '/view/', $item['uri']);
|
||||
|
72
bridges/OMonlineBridge.php
Normal file
72
bridges/OMonlineBridge.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
class OMonlineBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'OM Online Bridge';
|
||||
const URI = 'https://www.om-online.de';
|
||||
const DESCRIPTION = 'RSS feed for OM Online';
|
||||
const MAINTAINER = 'jummo4@yahoo.de';
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'ort' => [
|
||||
'name' => 'Ortsname',
|
||||
'title' => 'Für die Anzeige von Beitragen nur aus einem Ort oder mehreren Orten
|
||||
geben einen Orstnamen ein. Mehrere Ortsnamen müssen mit / getrennt eingeben werden,
|
||||
z.B. Vechta/Cloppenburg. Groß- und Kleinschreibung beachten!'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
if (!empty($this->getInput('ort'))) {
|
||||
$url = sprintf('%s/ort/%s', self::URI, $this->getInput('ort'));
|
||||
} else {
|
||||
$url = sprintf('%s', self::URI);
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$html = defaultLinkTo($html, $url);
|
||||
|
||||
foreach ($html->find('div.molecule-teaser > a ') as $index => $a) {
|
||||
$item = [];
|
||||
|
||||
$articlePath = $a->href;
|
||||
|
||||
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT)
|
||||
or returnServerError('Could not request: ' . $articlePath);
|
||||
|
||||
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
|
||||
|
||||
$contents = $articlePageHtml->find('div.molecule-article', 0);
|
||||
|
||||
$item['uri'] = $articlePath;
|
||||
$item['title'] = $contents->find('h1', 0)->innertext;
|
||||
|
||||
$contents->find('div.col-12 col-md-10 offset-0 offset-md-1', 0);
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate2($a->plaintext);
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDate2($text)
|
||||
{
|
||||
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
|
||||
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match($dateRegex, $text, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@@ -100,12 +100,14 @@ class PatreonBridge extends BridgeAbstract
|
||||
);
|
||||
$item['author'] = $user->full_name;
|
||||
|
||||
if (isset($post->attributes->image)) {
|
||||
$item['content'] .= '<p><a href="'
|
||||
. $post->attributes->url
|
||||
. '"><img src="'
|
||||
. $post->attributes->image->thumb_url
|
||||
. '" /></a></p>';
|
||||
$image = $post->attributes->image ?? null;
|
||||
if ($image) {
|
||||
$logo = sprintf(
|
||||
'<p><a href="%s"><img src="%s" /></a></p>',
|
||||
$post->attributes->url,
|
||||
$image->thumb_url ?? $image->url ?? $this->getURI()
|
||||
);
|
||||
$item['content'] .= $logo;
|
||||
}
|
||||
|
||||
if (isset($post->attributes->content)) {
|
||||
|
@@ -94,7 +94,7 @@ class PepperBridgeAbstract extends BridgeAbstract
|
||||
);
|
||||
|
||||
// If there is no results, we don't parse the content because it display some random deals
|
||||
$noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0);
|
||||
$noresult = $html->find('h3[class=size--all-l]', 0);
|
||||
if ($noresult != null && strpos($noresult->plaintext, $this->i8n('no-results')) !== false) {
|
||||
$this->items = [];
|
||||
} else {
|
||||
@@ -129,7 +129,7 @@ class PepperBridgeAbstract extends BridgeAbstract
|
||||
|
||||
// Find the text corresponding to the clock
|
||||
$spanDateDiv = $clock->parent()->find('span[class=hide--toW3]', 0);
|
||||
$itemDate = $spanDateDiv->plaintext;
|
||||
$itemDate = $spanDateDiv->plaintext ?? '';
|
||||
// In case of a Local deal, there is no date, but we can use
|
||||
// this case for other reason (like date not in the last field)
|
||||
if ($this->contains($itemDate, $this->i8n('localdeal'))) {
|
||||
@@ -481,12 +481,12 @@ HEREDOC;
|
||||
]
|
||||
);
|
||||
if ($deal->find('span[class*=' . $selector . ']', 0) != null) {
|
||||
return '<div>'
|
||||
. $deal->find('span[class*=' . $selector . ']', 0)->children(2)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
$children = $deal->find('span[class*=' . $selector . ']', 0)->children(2);
|
||||
if ($children) {
|
||||
return '<div>' . $children->plaintext . '</div>';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -58,17 +58,26 @@ class PicalaBridge extends BridgeAbstract
|
||||
{
|
||||
$fullhtml = getSimpleHTMLDOM($this->getURI());
|
||||
foreach ($fullhtml->find('.list-container-category a') as $article) {
|
||||
$srcsets = explode(',', $article->find('img', 0)->getAttribute('srcset'));
|
||||
$image = explode(' ', trim(array_shift($srcsets)))[0];
|
||||
$firstImage = $article->find('img', 0);
|
||||
$image = null;
|
||||
if ($firstImage !== null) {
|
||||
$srcsets = explode(',', $firstImage->getAttribute('srcset'));
|
||||
$image = explode(' ', trim(array_shift($srcsets)))[0];
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = self::URI . $article->href;
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
$item['content'] = sprintf(
|
||||
'<img src="%s" /><br>%s',
|
||||
$image,
|
||||
$article->find('.teaser__text', 0)->plaintext
|
||||
);
|
||||
if ($image === null) {
|
||||
$item['content'] = $article->find('.teaser__text', 0)->plaintext;
|
||||
} else {
|
||||
$item['content'] = sprintf(
|
||||
'<img src="%s" /><br>%s',
|
||||
$image,
|
||||
$article->find('.teaser__text', 0)->plaintext
|
||||
);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -47,37 +47,36 @@ class PicnobBridge extends BridgeAbstract
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
foreach ($html->find('.items') as $part) {
|
||||
foreach ($part->find('.item') as $element) {
|
||||
$url = urljoin(self::URI, $element->find('a', 0)->href);
|
||||
$url = urljoin(self::URI, $element->find('a', 0)->href);
|
||||
|
||||
$date = date_create();
|
||||
$relativeDate = date_interval_create_from_date_string(str_replace(' ago', '', $element->find('.time', 0)->plaintext));
|
||||
if ($relativeDate) {
|
||||
date_sub($date, $relativeDate);
|
||||
}
|
||||
|
||||
$date = date_create();
|
||||
$relativeDate = str_replace(' ago', '', $element->find('.time', 0)->plaintext);
|
||||
date_sub($date, date_interval_create_from_date_string($relativeDate));
|
||||
$description = defaultLinkTo(trim($element->find('.sum', 0)->innertext), self::URI);
|
||||
|
||||
$description = defaultLinkTo(trim($element->find('.sum', 0)->innertext), self::URI);
|
||||
$isVideo = (bool) $element->find('.icon_video', 0);
|
||||
$videoNote = $isVideo ? '<p><i>(video)</i></p>' : '';
|
||||
|
||||
$isVideo = (bool) $element->find('.icon_video', 0);
|
||||
$videoNote = $isVideo ? '<p><i>(video)</i></p>' : '';
|
||||
$isTV = (bool) $element->find('.icon_tv', 0);
|
||||
$tvNote = $isTV ? '<p><i>(TV)</i></p>' : '';
|
||||
|
||||
$isTV = (bool) $element->find('.icon_tv', 0);
|
||||
$tvNote = $isTV ? '<p><i>(TV)</i></p>' : '';
|
||||
$isMoreContent = (bool) $element->find('.icon_multi', 0);
|
||||
$moreContentNote = $isMoreContent ? '<p><i>(multiple images and/or videos)</i></p>' : '';
|
||||
|
||||
$isMoreContent = (bool) $element->find('.icon_multi', 0);
|
||||
$moreContentNote = $isMoreContent ? '<p><i>(multiple images and/or videos)</i></p>' : '';
|
||||
$imageUrl = $element->find('.img', 0)->getAttribute('data-src');
|
||||
|
||||
$imageUrl = $element->find('.img', 0)->getAttribute('data-src');
|
||||
parse_str(parse_url($imageUrl, PHP_URL_QUERY), $imageVars);
|
||||
$imageUrl = $imageVars['u'];
|
||||
$uid = explode('/', parse_url($url, PHP_URL_PATH))[2];
|
||||
|
||||
$uid = explode('/', parse_url($url, PHP_URL_PATH))[2];
|
||||
|
||||
$this->items[] = [
|
||||
'uri' => $url,
|
||||
'timestamp' => date_format($date, 'r'),
|
||||
'title' => strlen($description) > 60 ? mb_substr($description, 0, 57) . '...' : $description,
|
||||
'thumbnail' => $imageUrl,
|
||||
'enclosures' => [$imageUrl],
|
||||
'content' => <<<HTML
|
||||
$this->items[] = [
|
||||
'uri' => $url,
|
||||
'timestamp' => date_format($date, 'r'),
|
||||
'title' => strlen($description) > 60 ? mb_substr($description, 0, 57) . '...' : $description,
|
||||
'thumbnail' => $imageUrl,
|
||||
'enclosures' => [$imageUrl],
|
||||
'content' => <<<HTML
|
||||
<a href="{$url}">
|
||||
<img loading="lazy" src="{$imageUrl}" />
|
||||
</a>
|
||||
@@ -86,8 +85,8 @@ class PicnobBridge extends BridgeAbstract
|
||||
{$moreContentNote}
|
||||
<p>{$description}<p>
|
||||
HTML,
|
||||
'uid' => $uid
|
||||
];
|
||||
'uid' => $uid
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,7 +67,9 @@ class PornhubBridge extends BridgeAbstract
|
||||
|
||||
$show_images = $this->getInput('show_images');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
$html = getSimpleHTMLDOM($uri, [
|
||||
'cookie: accessAgeDisclaimerPH=1'
|
||||
]);
|
||||
|
||||
foreach ($html->find('div.videoUList ul.videos li.videoblock') as $element) {
|
||||
$item = [];
|
||||
|
@@ -61,9 +61,9 @@ class PresidenciaPTBridge extends BridgeAbstract
|
||||
$item = [];
|
||||
|
||||
$link = $element->find('a', 0);
|
||||
$etitle = $element->find('.content-box h2', 0);
|
||||
$edts = $element->find('p', 1);
|
||||
$edt = html_entity_decode($edts->innertext, ENT_HTML5);
|
||||
$etitle = $element->find('.article-title', 0);
|
||||
$edts = $element->find('.date', 0);
|
||||
$edt = $edts->innertext;
|
||||
|
||||
$item['title'] = strip_tags($etitle->innertext);
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
|
40
bridges/RainLoopBridge.php
Normal file
40
bridges/RainLoopBridge.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class RainLoopBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Simounet';
|
||||
const NAME = 'RainLoop';
|
||||
const URI_BASE = 'https://www.rainloop.net';
|
||||
const URI = self::URI_BASE . '/changelog/';
|
||||
const CACHE_TIMEOUT = 21600; //6h
|
||||
const DESCRIPTION = 'RainLoop\'s changelog';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$mainContent = $html->find('.main-center', 0);
|
||||
$elements = $mainContent->find('.row-fluid');
|
||||
foreach ($elements as $i => $element) {
|
||||
if ($i === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$titleEl = $element->find('.h3', 0);
|
||||
$title = is_object($titleEl) ? $titleEl->plaintext : '';
|
||||
|
||||
$postUrl = self::URI . $title;
|
||||
|
||||
$contentEl = $element->find('.span9', 0);
|
||||
$content = is_object($contentEl) ? $contentEl->xmltext() : '';
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $postUrl;
|
||||
$item['title'] = $title;
|
||||
$item['content'] = $content;
|
||||
$item['timestamp'] = strtotime('now');
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -82,10 +82,10 @@ class Releases3DSBridge extends BridgeAbstract
|
||||
$item = [];
|
||||
$item['title'] = $name;
|
||||
$item['author'] = $publisher;
|
||||
$item['timestamp'] = $ignDate;
|
||||
$item['enclosures'] = [$ignCoverArt];
|
||||
//$item['timestamp'] = $ignDate;
|
||||
//$item['enclosures'] = [$ignCoverArt];
|
||||
$item['uri'] = empty($ignLink) ? $searchLinkDuckDuckGo : $ignLink;
|
||||
$item['content'] = $ignDescription . $releaseDescription . $releaseSearchLinks;
|
||||
$item['content'] = $releaseDescription . $releaseSearchLinks;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
97
bridges/RemixAudioBridge.php
Normal file
97
bridges/RemixAudioBridge.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
class RemixAudioBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Simounet';
|
||||
const NAME = 'RemixAudio';
|
||||
const URI = 'https://remix.audio';
|
||||
const CACHE_TIMEOUT = 0; //6h
|
||||
//const CACHE_TIMEOUT = 21600; //6h
|
||||
const DESCRIPTION = 'RemixAudio profiles';
|
||||
const PROFILE_QUERY_PARAM = 'profile';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
self::PROFILE_QUERY_PARAM => [
|
||||
'name' => 'Profile',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'Amoraboy',
|
||||
'required' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
private $feedTitle = null;
|
||||
private $feedIcon = null;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
$user = $this->getUser($html);
|
||||
|
||||
$this->feedTitle = $user['name'];
|
||||
$this->feedIcon = $user['avatar'];
|
||||
|
||||
$elements = $html->find('.song-container');
|
||||
foreach ($elements as $element) {
|
||||
$titleEl = $element->find('.song-title', 0);
|
||||
$title = is_object($titleEl) ? $titleEl->plaintext : '';
|
||||
|
||||
$urlEl = $titleEl->find('a', 0);
|
||||
|
||||
$publishedEl = $element->find('.timeago', 0);
|
||||
|
||||
$songEl = $element->find('.song-play-btn', 0);
|
||||
$song = is_object($songEl) ? '<audio controls><source src="' . $songEl->getAttribute('data-track-url') . '" type="audio/mpeg" /></audio>' : '';
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $urlEl->href;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = strtotime($publishedEl->title);
|
||||
$item['content'] = '<p>' . $user['name'] . ' - ' . $title . '</p>' . $song;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
if ($this->feedIcon) {
|
||||
return $this->feedIcon;
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->feedTitle) {
|
||||
return $this->feedTitle . ' - ' . self::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
$profile = $this->getProfile();
|
||||
if ($profile) {
|
||||
return self::URI . '/profile/' . $profile;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
private function getUser($html)
|
||||
{
|
||||
return [
|
||||
'avatar' => $html->find('.cover-avatar img', 0)->src,
|
||||
'name' => $html->find('.cover-username a', 0)->plaintext
|
||||
];
|
||||
}
|
||||
|
||||
private function getProfile()
|
||||
{
|
||||
return $this->getInput(self::PROFILE_QUERY_PARAM);
|
||||
}
|
||||
}
|
@@ -342,15 +342,12 @@ class ReutersBridge extends BridgeAbstract
|
||||
{
|
||||
$img_placeholder = '';
|
||||
|
||||
foreach ($images as $image) { // Add more image to article.
|
||||
foreach ($images as $image) {
|
||||
// Add more image to article.
|
||||
$image_url = $image['url'];
|
||||
$image_caption = $image['caption'];
|
||||
$image_caption = $image['caption'] ?? $image['alt_text'] ?? $image['subtitle'] ?? '';
|
||||
$image_alt_text = '';
|
||||
if (isset($image['alt_text'])) {
|
||||
$image_alt_text = $image['alt_text'];
|
||||
} else {
|
||||
$image_alt_text = $image_caption;
|
||||
}
|
||||
$image_alt_text = $image['alt_text'] ?? $image_caption;
|
||||
$img = "<img src=\"$image_url\" alt=\"$image_alt_text\">";
|
||||
$img_caption = "<figcaption style=\"text-align: center;\"><i>$image_caption</i></figcaption>";
|
||||
$figure = "<figure>$img \t $img_caption</figure>";
|
||||
@@ -557,7 +554,11 @@ EOD;
|
||||
$image_placeholder = $this->handleImage([$story['thumbnail']]);
|
||||
}
|
||||
$content = $story['description'] . $image_placeholder;
|
||||
$category = [$story['primary_section']['name']];
|
||||
if (isset($story['primary_section']['name'])) {
|
||||
$category = [$story['primary_section']['name']];
|
||||
} else {
|
||||
$category = [];
|
||||
}
|
||||
} else {
|
||||
$content_detail = $this->getArticle($article_uri);
|
||||
$description = $content_detail['content'];
|
||||
|
@@ -49,7 +49,7 @@ class RoadAndTrackBridge extends BridgeAbstract
|
||||
$item['title'] = $title->innertext;
|
||||
}
|
||||
|
||||
$item['author'] = $article->find('.byline-name', 0)->innertext;
|
||||
$item['author'] = $article->find('.byline-name', 0)->innertext ?? '';
|
||||
$item['timestamp'] = strtotime($article->find('.content-info-date', 0)->getAttribute('datetime'));
|
||||
|
||||
$content = $article->find('.content-container', 0);
|
||||
|
@@ -40,9 +40,12 @@ class RumbleBridge extends BridgeAbstract
|
||||
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
foreach ($dom->find('li.video-listing-entry') as $video) {
|
||||
$datetime = $video->find('time', 0)->getAttribute('datetime');
|
||||
|
||||
$this->items[] = [
|
||||
'title' => $video->find('h3', 0)->plaintext,
|
||||
'uri' => self::URI . $video->find('a', 0)->href,
|
||||
'timestamp' => (new \DateTimeImmutable($datetime))->getTimestamp(),
|
||||
'author' => $account . '@rumble.com',
|
||||
'content' => defaultLinkTo($video, self::URI)->innertext,
|
||||
];
|
||||
|
@@ -43,10 +43,6 @@ class SchweinfurtBuergerinformationenBridge extends BridgeAbstract
|
||||
|
||||
foreach ($articleIDs as $articleID) {
|
||||
$this->items[] = $this->generateItemFromArticle($articleID);
|
||||
|
||||
if (Debug::isEnabled()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
138
bridges/ScribbleHubBridge.php
Normal file
138
bridges/ScribbleHubBridge.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
class ScribbleHubBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'phantop';
|
||||
const NAME = 'Scribble Hub';
|
||||
const URI = 'https://scribblehub.com/';
|
||||
const DESCRIPTION = 'Returns chapters from Scribble Hub.';
|
||||
const PARAMETERS = [
|
||||
'All' => [],
|
||||
'Author' => [
|
||||
'uid' => [
|
||||
'name' => 'uid',
|
||||
'required' => true,
|
||||
// Example: Alyson Greaves's stories
|
||||
'exampleValue' => '76208',
|
||||
],
|
||||
],
|
||||
'Series' => [
|
||||
'sid' => [
|
||||
'name' => 'sid',
|
||||
'required' => true,
|
||||
// Example: latest chapters from The Sisters of Dorley by Alyson Greaves
|
||||
'exampleValue' => '421879',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return self::URI . 'favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = 'https://rssscribblehub.com/rssfeed.php?type=';
|
||||
if ($this->queriedContext === 'Author') {
|
||||
$url = $url . 'author&uid=' . $this->getInput('uid');
|
||||
} else { //All and Series use the same source feed
|
||||
$url = $url . 'main';
|
||||
}
|
||||
$this->collectExpandableDatas($url);
|
||||
}
|
||||
|
||||
protected function parseItem($newItem)
|
||||
{
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
//For series, filter out other series from 'All' feed
|
||||
if (
|
||||
$this->queriedContext === 'Series'
|
||||
&& preg_match('/read\/' . $this->getInput('sid') . '-/', $item['uri']) !== 1
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$item['comments'] = $item['uri'] . '#comments';
|
||||
|
||||
try {
|
||||
$item_html = getSimpleHTMLDOMCached($item['uri']);
|
||||
} catch (HttpException $e) {
|
||||
// 403 Forbidden, This means we got anti-bot response
|
||||
if ($e->getCode() === 403) {
|
||||
return $item;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$item_html = defaultLinkTo($item_html, self::URI);
|
||||
|
||||
//Retrieve full description from page contents
|
||||
$item['content'] = $item_html->find('#chp_raw', 0);
|
||||
|
||||
//Retrieve image for thumbnail
|
||||
$item_image = $item_html->find('.s_novel_img > img', 0)->src;
|
||||
$item['enclosures'] = [$item_image];
|
||||
|
||||
//Restore lost categories
|
||||
$item_story = html_entity_decode($item_html->find('.chp_byauthor > a', 0)->innertext);
|
||||
$item_sid = $item_html->find('#mysid', 0)->value;
|
||||
$item['categories'] = [$item_story, $item_sid];
|
||||
|
||||
//Generate UID
|
||||
$item_pid = $item_html->find('#mypostid', 0)->value;
|
||||
$item['uid'] = $item_sid . "/$item_pid";
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$name = parent::getName() . " $this->queriedContext";
|
||||
switch ($this->queriedContext) {
|
||||
case 'Author':
|
||||
try {
|
||||
$page = getSimpleHTMLDOMCached(self::URI . 'profile/' . $this->getInput('uid'));
|
||||
} catch (HttpException $e) {
|
||||
// 403 Forbidden, This means we got anti-bot response
|
||||
if ($e->getCode() === 403) {
|
||||
return $name;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
$title = html_entity_decode($page->find('.p_m_username.fp_authorname', 0)->plaintext);
|
||||
break;
|
||||
case 'Series':
|
||||
try {
|
||||
$page = getSimpleHTMLDOMCached(self::URI . 'series/' . $this->getInput('sid') . '/a');
|
||||
} catch (HttpException $e) {
|
||||
// 403 Forbidden, This means we got anti-bot response
|
||||
if ($e->getCode() === 403) {
|
||||
return $item;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
$title = html_entity_decode($page->find('.fic_title', 0)->plaintext);
|
||||
break;
|
||||
}
|
||||
if (isset($title)) {
|
||||
$name .= " - $title";
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
$uri = parent::getURI();
|
||||
switch ($this->queriedContext) {
|
||||
case 'Author':
|
||||
$uri = self::URI . 'profile/' . $this->getInput('uid');
|
||||
break;
|
||||
case 'Series':
|
||||
$uri = self::URI . 'series/' . $this->getInput('sid') . '/a';
|
||||
break;
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
}
|
44
bridges/SleeperFantasyFootballBridge.php
Normal file
44
bridges/SleeperFantasyFootballBridge.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class SleeperFantasyFootballBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Sleeper.com Alerts';
|
||||
const URI = 'https://sleeper.com/topics/170000000000000000';
|
||||
const DESCRIPTION = 'Fantasy Football Alerts from Sleeper.com';
|
||||
const MAINTAINER = 'piyushpaliwal';
|
||||
const PARAMETERS = [];
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached(self::URI, self::CACHE_TIMEOUT);
|
||||
foreach ($html->find('div.content > div.latest-topics > a') as $index => $a) {
|
||||
$content = $a->find('div.title > p', 0)->innertext;
|
||||
$meta = $this->processString($a->find('div.desc > div.username', 0)->innertext);
|
||||
$item['title'] = $content;
|
||||
$item['content'] = $content;
|
||||
$item['categories'] = $a->find('div.title div.tag', 0)->innertext;
|
||||
$item['timestamp'] = $meta['timestamp'];
|
||||
$item['author'] = $meta['author'];
|
||||
$item['enclosures'] = $a->find('div.player-photo amp-img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function processString($inputString)
|
||||
{
|
||||
$decodedString = str_replace([' ', '•'], [' ', '|'], $inputString);
|
||||
$splitArray = explode(' | ', $decodedString);
|
||||
$author = trim($splitArray[0]);
|
||||
$timeString = trim($splitArray[1]);
|
||||
$timestamp = strtotime($timeString);
|
||||
return [
|
||||
'author' => $author,
|
||||
'timestamp' => $timestamp
|
||||
];
|
||||
}
|
||||
}
|
@@ -36,13 +36,17 @@ class SoundCloudBridge extends BridgeAbstract
|
||||
|
||||
private $feedTitle = null;
|
||||
private $feedIcon = null;
|
||||
private $clientIDCache = null;
|
||||
private $cache = null;
|
||||
|
||||
private $clientIdRegex = '/client_id.*?"(.+?)"/';
|
||||
private $widgetRegex = '/widget-.+?\.js/';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$this->cache = RssBridge::getCache();
|
||||
$this->cache->setScope('SoundCloudBridge');
|
||||
$this->cache->setKey(['client_id']);
|
||||
|
||||
$res = $this->getUser($this->getInput('u'));
|
||||
|
||||
$this->feedTitle = $res->username;
|
||||
@@ -62,8 +66,7 @@ class SoundCloudBridge extends BridgeAbstract
|
||||
$item['author'] = $apiItem->user->username;
|
||||
$item['title'] = $apiItem->user->username . ' - ' . $apiItem->title;
|
||||
$item['timestamp'] = strtotime($apiItem->created_at);
|
||||
|
||||
$description = nl2br($apiItem->description);
|
||||
$description = nl2br($apiItem->description ?? '');
|
||||
|
||||
$item['content'] = <<<HTML
|
||||
<p>{$description}</p>
|
||||
@@ -116,24 +119,11 @@ HTML;
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function initClientIDCache()
|
||||
{
|
||||
if ($this->clientIDCache !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$this->clientIDCache = $cacheFactory->create();
|
||||
$this->clientIDCache->setScope('SoundCloudBridge');
|
||||
$this->clientIDCache->setKey(['client_id']);
|
||||
}
|
||||
|
||||
private function getClientID()
|
||||
{
|
||||
$this->initClientIDCache();
|
||||
|
||||
$clientID = $this->clientIDCache->loadData();
|
||||
$this->cache->setScope('SoundCloudBridge');
|
||||
$this->cache->setKey(['client_id']);
|
||||
$clientID = $this->cache->loadData();
|
||||
|
||||
if ($clientID == null) {
|
||||
return $this->refreshClientID();
|
||||
@@ -144,8 +134,6 @@ HTML;
|
||||
|
||||
private function refreshClientID()
|
||||
{
|
||||
$this->initClientIDCache();
|
||||
|
||||
$playerHTML = getContents($this->playerUrl);
|
||||
|
||||
// Extract widget JS filenames from player page
|
||||
@@ -163,7 +151,9 @@ HTML;
|
||||
|
||||
if (preg_match($this->clientIdRegex, $widgetJS, $matches)) {
|
||||
$clientID = $matches[1];
|
||||
$this->clientIDCache->saveData($clientID);
|
||||
$this->cache->setScope('SoundCloudBridge');
|
||||
$this->cache->setKey(['client_id']);
|
||||
$this->cache->saveData($clientID);
|
||||
|
||||
return $clientID;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ class SpotifyBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Spotify';
|
||||
const URI = 'https://spotify.com/';
|
||||
const DESCRIPTION = 'Fetches the latest albums from one or more artists or the latest tracks from one or more playlists';
|
||||
const DESCRIPTION = 'Fetches the latest items from one or more artists, playlists or podcasts';
|
||||
const MAINTAINER = 'Paroleen';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = [ [
|
||||
@@ -19,7 +19,7 @@ class SpotifyBridge extends BridgeAbstract
|
||||
'required' => true
|
||||
],
|
||||
'country' => [
|
||||
'name' => 'Country',
|
||||
'name' => 'Country/Market',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'exampleValue' => 'US',
|
||||
@@ -36,7 +36,7 @@ class SpotifyBridge extends BridgeAbstract
|
||||
'name' => 'Spotify URIs',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'spotify:artist:4lianjyuR1tqf6oUX8kjrZ [,spotify:playlist:37i9dQZF1DXcBWIGoYBM5M]',
|
||||
'exampleValue' => 'spotify:artist:4lianjyuR1tqf6oUX8kjrZ [,spotify:playlist:37i9dQZF1DXcBWIGoYBM5M,spotify:show:6ShFMYxeDNMo15COLObDvC]',
|
||||
],
|
||||
'albumtype' => [
|
||||
'name' => 'Album type',
|
||||
@@ -47,14 +47,199 @@ class SpotifyBridge extends BridgeAbstract
|
||||
]
|
||||
] ];
|
||||
|
||||
const TOKENURI = 'https://accounts.spotify.com/api/token';
|
||||
const APIURI = 'https://api.spotify.com/v1/';
|
||||
|
||||
private $uri = '';
|
||||
private $name = '';
|
||||
private $token = '';
|
||||
private $uris = [];
|
||||
private $entries = [];
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$entries = $this->getAllEntries();
|
||||
usort($entries, function ($entry1, $entry2) {
|
||||
return $this->getDate($entry2) <=> $this->getDate($entry1);
|
||||
});
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (! isset($entry['type'])) {
|
||||
$item = $this->getTrackData($entry);
|
||||
} elseif ($entry['type'] === 'album') {
|
||||
$item = $this->getAlbumData($entry);
|
||||
} elseif ($entry['type'] === 'episode') {
|
||||
$item = $this->getEpisodeData($entry);
|
||||
} else {
|
||||
throw new \Exception('Spotify URI not supported');
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($this->getInput('limit') > 0 && count($this->items) >= $this->getInput('limit')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllEntries()
|
||||
{
|
||||
$entries = [];
|
||||
$uris = explode(',', $this->getInput('spotifyuri'));
|
||||
|
||||
foreach ($uris as $uri) {
|
||||
$type = explode(':', $uri)[1];
|
||||
$spotifyId = explode(':', $uri)[2];
|
||||
|
||||
$types = [
|
||||
'artist' => 'album',
|
||||
'playlist' => 'track',
|
||||
'show' => 'episode',
|
||||
];
|
||||
if (!isset($types[$type])) {
|
||||
throw new \Exception('Spotify URI not supported');
|
||||
}
|
||||
$entry_type = $types[$type];
|
||||
|
||||
$url = 'https://api.spotify.com/v1/' . $type . 's/' . $spotifyId . '/' . $entry_type . 's';
|
||||
$query = [
|
||||
'limit' => 50,
|
||||
];
|
||||
|
||||
if ($type === 'artist') {
|
||||
$query['country'] = $this->getInput('country');
|
||||
$query['include_groups'] = $this->getInput('albumtype');
|
||||
} else {
|
||||
$query['market'] = $this->getInput('country');
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
while (true) {
|
||||
$query['offset'] = $offset;
|
||||
$partial = $this->fetchContent($url . '?' . http_build_query($query));
|
||||
if (empty($partial['items'])) {
|
||||
break;
|
||||
}
|
||||
$entries = array_merge($entries, $partial['items']);
|
||||
$offset += 50;
|
||||
}
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private function getAlbumData($album)
|
||||
{
|
||||
$item = [];
|
||||
$item['title'] = $album['name'];
|
||||
$item['uri'] = $album['external_urls']['spotify'];
|
||||
$item['timestamp'] = $this->getDate($album);
|
||||
$item['author'] = $album['artists'][0]['name'];
|
||||
$item['categories'] = [$album['album_type']];
|
||||
$item['content'] = '<img style="width: 256px" src="' . $album['images'][0]['url'] . '">';
|
||||
if ($album['total_tracks'] > 1) {
|
||||
$item['content'] .= '<p>Total tracks: ' . $album['total_tracks'] . '</p>';
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getTrackData($track)
|
||||
{
|
||||
$item = [];
|
||||
$item['title'] = $track['track']['name'];
|
||||
$item['uri'] = $track['track']['external_urls']['spotify'];
|
||||
$item['timestamp'] = $this->getDate($track);
|
||||
$item['author'] = $track['track']['artists'][0]['name'];
|
||||
$item['categories'] = ['track'];
|
||||
$item['content'] = '<img style="width: 256px" src="' . $track['track']['album']['images'][0]['url'] . '">';
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getEpisodeData($episode)
|
||||
{
|
||||
$item = [];
|
||||
$item['title'] = $episode['name'];
|
||||
$item['uri'] = $episode['external_urls']['spotify'];
|
||||
$item['timestamp'] = $this->getDate($episode);
|
||||
$item['content'] = '<img style="width: 256px" src="' . $episode['images'][0]['url'] . '">';
|
||||
if (isset($episode['description'])) {
|
||||
$item['content'] = $item['content'] . '<p>' . $episode['description'] . '</p>';
|
||||
}
|
||||
if (isset($episode['audio_preview_url'])) {
|
||||
$item['content'] = $item['content'] . '<audio controls src="' . $episode['audio_preview_url'] . '"></audio>';
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getDate($entry)
|
||||
{
|
||||
if (isset($entry['type'])) {
|
||||
$type = 'release_date';
|
||||
} else {
|
||||
$type = 'added_at';
|
||||
}
|
||||
|
||||
$date = $entry[$type];
|
||||
|
||||
if (strlen($date) == 4) {
|
||||
$date .= '-01-01';
|
||||
} elseif (strlen($date) == 7) {
|
||||
$date .= '-01';
|
||||
}
|
||||
|
||||
if (strlen($date) > 10) {
|
||||
return DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $date)->getTimestamp();
|
||||
}
|
||||
|
||||
return DateTime::createFromFormat('Y-m-d', $date)->getTimestamp();
|
||||
}
|
||||
|
||||
private function getToken()
|
||||
{
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('SpotifyBridge');
|
||||
|
||||
$cacheKey = sprintf('%s:%s', $this->getInput('clientid'), $this->getInput('clientsecret'));
|
||||
$cache->setKey([$cacheKey]);
|
||||
|
||||
$time = null;
|
||||
if ($cache->getTime()) {
|
||||
$time = (new DateTime())->getTimestamp() - $cache->getTime();
|
||||
}
|
||||
|
||||
if (!$cache->getTime() || $time >= 3600) {
|
||||
$this->fetchToken();
|
||||
$cache->saveData($this->token);
|
||||
} else {
|
||||
$this->token = $cache->loadData();
|
||||
}
|
||||
}
|
||||
|
||||
private function fetchToken()
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, 'https://accounts.spotify.com/api/token');
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_POST, 1);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
|
||||
|
||||
$basic = sprintf('%s:%s', $this->getInput('clientid'), $this->getInput('clientsecret'));
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Basic ' . base64_encode($basic)]);
|
||||
|
||||
$json = curl_exec($curl);
|
||||
$json = json_decode($json)->access_token;
|
||||
curl_close($curl);
|
||||
|
||||
$this->token = $json;
|
||||
}
|
||||
|
||||
private function fetchContent($url)
|
||||
{
|
||||
$this->getToken();
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $this->token]);
|
||||
$json = curl_exec($curl);
|
||||
$json = json_decode($json, true);
|
||||
curl_close($curl);
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
@@ -74,83 +259,22 @@ class SpotifyBridge extends BridgeAbstract
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.scdn.co/i/_global/favicon.png';
|
||||
}
|
||||
|
||||
private function getUriType($uri)
|
||||
{
|
||||
return explode(':', $uri)[1];
|
||||
}
|
||||
|
||||
private function getId($uri)
|
||||
{
|
||||
return explode(':', $uri)[2];
|
||||
}
|
||||
|
||||
private function getDate($entry)
|
||||
{
|
||||
if ($entry['type'] === 'album') {
|
||||
$date = $entry['release_date'];
|
||||
} else {
|
||||
$date = $entry['added_at'];
|
||||
}
|
||||
|
||||
if (strlen($date) == 4) {
|
||||
$date .= '-01-01';
|
||||
} elseif (strlen($date) == 7) {
|
||||
$date .= '-01';
|
||||
}
|
||||
|
||||
if (strlen($date) > 10) {
|
||||
return DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $date)->getTimestamp();
|
||||
}
|
||||
|
||||
return DateTime::createFromFormat('Y-m-d', $date)->getTimestamp();
|
||||
}
|
||||
|
||||
private function getAlbumType()
|
||||
{
|
||||
return $this->getInput('albumtype');
|
||||
}
|
||||
|
||||
private function getCountry()
|
||||
{
|
||||
return $this->getInput('country');
|
||||
}
|
||||
|
||||
private function getToken()
|
||||
{
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache->setScope('SpotifyBridge');
|
||||
$cache->setKey(['token']);
|
||||
|
||||
if ($cache->getTime()) {
|
||||
$time = (new DateTime())->getTimestamp() - $cache->getTime();
|
||||
Debug::log('Token time: ' . $time);
|
||||
}
|
||||
|
||||
if ($cache->getTime() == false || $time >= 3600) {
|
||||
Debug::log('Fetching token from Spotify');
|
||||
$this->fetchToken();
|
||||
$cache->saveData($this->token);
|
||||
} else {
|
||||
Debug::log('Loading token from cache');
|
||||
$this->token = $cache->loadData();
|
||||
}
|
||||
|
||||
Debug::log('Token: ' . $this->token);
|
||||
}
|
||||
|
||||
private function getFirstEntry()
|
||||
{
|
||||
$uris = explode(',', $this->getInput('spotifyuri'));
|
||||
if (!is_null($this->getInput('spotifyuri')) && strpos($this->getInput('spotifyuri'), ',') === false) {
|
||||
$type = $this->getUriType($this->uris[0]) . 's';
|
||||
$item = $this->fetchContent(self::APIURI . $type . '/'
|
||||
. $this->getId($this->uris[0]));
|
||||
$firstUri = $uris[0];
|
||||
$type = explode(':', $firstUri)[1];
|
||||
$spotifyId = explode(':', $firstUri)[2];
|
||||
|
||||
$uri = 'https://api.spotify.com/v1/' . $type . 's/' . $spotifyId;
|
||||
$query = [];
|
||||
if ($type === 'show') {
|
||||
$query['market'] = $this->getInput('country');
|
||||
}
|
||||
|
||||
$item = $this->fetchContent($uri . '?' . http_build_query($query));
|
||||
|
||||
$this->uri = $item['external_urls']['spotify'];
|
||||
$this->name = $item['name'] . ' - Spotify';
|
||||
} else {
|
||||
@@ -159,165 +283,8 @@ class SpotifyBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllUris()
|
||||
public function getIcon()
|
||||
{
|
||||
Debug::log('Parsing all uris');
|
||||
$this->uris = explode(',', $this->getInput('spotifyuri'));
|
||||
}
|
||||
|
||||
private function getAllEntries()
|
||||
{
|
||||
$this->entries = [];
|
||||
|
||||
$this->getAllUris();
|
||||
|
||||
Debug::log('Fetching all entries');
|
||||
foreach ($this->uris as $uri) {
|
||||
$type = $this->getUriType($uri) . 's';
|
||||
$entry_type = $type === 'artists' ? 'albums' : 'tracks';
|
||||
$fetch = true;
|
||||
$offset = 0;
|
||||
|
||||
$api_url = self::APIURI . $type . '/'
|
||||
. $this->getId($uri)
|
||||
. '/' . $entry_type
|
||||
. '?limit=50&country='
|
||||
. $this->getCountry();
|
||||
|
||||
if ($type === 'artists') {
|
||||
$api_url = $api_url . '&include_groups=' . $this->getAlbumType();
|
||||
}
|
||||
|
||||
while ($fetch) {
|
||||
$partial = $this->fetchContent($api_url
|
||||
. '&offset='
|
||||
. $offset);
|
||||
|
||||
if (!empty($partial['items'])) {
|
||||
$this->entries = array_merge(
|
||||
$this->entries,
|
||||
$partial['items']
|
||||
);
|
||||
} else {
|
||||
$fetch = false;
|
||||
}
|
||||
|
||||
$offset += 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fetchToken()
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, self::TOKENURI);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_POST, 1);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Basic '
|
||||
. base64_encode($this->getInput('clientid')
|
||||
. ':'
|
||||
. $this->getInput('clientsecret'))]);
|
||||
|
||||
$json = curl_exec($curl);
|
||||
$json = json_decode($json)->access_token;
|
||||
curl_close($curl);
|
||||
|
||||
$this->token = $json;
|
||||
}
|
||||
|
||||
private function fetchContent($url)
|
||||
{
|
||||
$this->getToken();
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer '
|
||||
. $this->token]);
|
||||
|
||||
Debug::log('Fetching content from ' . $url);
|
||||
$json = curl_exec($curl);
|
||||
$json = json_decode($json, true);
|
||||
curl_close($curl);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function sortEntries()
|
||||
{
|
||||
Debug::log('Sorting entries');
|
||||
usort($this->entries, function ($entry1, $entry2) {
|
||||
if ($this->getDate($entry1) < $this->getDate($entry2)) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function getAlbumData($album)
|
||||
{
|
||||
$item = [];
|
||||
$item['title'] = $album['name'];
|
||||
$item['uri'] = $album['external_urls']['spotify'];
|
||||
|
||||
$item['timestamp'] = $this->getDate($album);
|
||||
$item['author'] = $album['artists'][0]['name'];
|
||||
$item['categories'] = [$album['album_type']];
|
||||
|
||||
$item['content'] = '<img style="width: 256px" src="'
|
||||
. $album['images'][0]['url']
|
||||
. '">';
|
||||
|
||||
if ($album['total_tracks'] > 1) {
|
||||
$item['content'] .= '<p>Total tracks: '
|
||||
. $album['total_tracks']
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getTrackData($track)
|
||||
{
|
||||
$item = [];
|
||||
|
||||
$item['title'] = $track['track']['name'];
|
||||
$item['uri'] = $track['track']['external_urls']['spotify'];
|
||||
|
||||
$item['timestamp'] = $this->getDate($track);
|
||||
$item['author'] = $track['track']['artists'][0]['name'];
|
||||
$item['categories'] = ['track'];
|
||||
|
||||
$item['content'] = '<img style="width: 256px" src="'
|
||||
. $track['track']['album']['images'][0]['url']
|
||||
. '">';
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
$this->getAllEntries();
|
||||
$this->sortEntries();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach ($this->entries as $entry) {
|
||||
if ($entry['type'] === 'album') {
|
||||
$item = $this->getAlbumData($entry);
|
||||
} else {
|
||||
$item = $this->getTrackData($entry);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($this->getInput('limit') > 0 && count($this->items) >= $this->getInput('limit')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 'https://www.scdn.co/i/_global/favicon.png';
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,8 @@ class TheHackerNewsBridge extends BridgeAbstract
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
$html = convertLazyLoading($html);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
$limit = 0;
|
||||
|
||||
foreach ($html->find('div.body-post') as $element) {
|
||||
@@ -17,74 +19,68 @@ class TheHackerNewsBridge extends BridgeAbstract
|
||||
break;
|
||||
}
|
||||
|
||||
// Author (not present on home page)
|
||||
$article_author = null;
|
||||
$icon_user = $element->find('i.icon-user', 0);
|
||||
if ($icon_user) {
|
||||
$article_author = trim($icon_user->parent()->plaintext);
|
||||
$article_author = str_replace('', '', $article_author);
|
||||
}
|
||||
|
||||
// Title
|
||||
$article_title = $element->find('h2.home-title', 0)->plaintext;
|
||||
|
||||
// Date
|
||||
$article_timestamp = time();
|
||||
//Date without time
|
||||
$calendar = $element->find('i.icon-calendar', 0);
|
||||
if ($calendar) {
|
||||
$article_timestamp = strtotime(
|
||||
extractFromDelimiters(
|
||||
$calendar->parent()->outertext,
|
||||
'</i>',
|
||||
'<span>'
|
||||
'</span>'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//Article thumbnail in lazy-loading image
|
||||
if (is_object($element->find('img[data-echo]', 0))) {
|
||||
$article_thumbnail = [
|
||||
extractFromDelimiters(
|
||||
$element->find('img[data-echo]', 0)->outertext,
|
||||
"data-echo='",
|
||||
"'"
|
||||
)
|
||||
];
|
||||
} else {
|
||||
$article_thumbnail = [];
|
||||
// Thumbnail
|
||||
$article_thumbnail = [];
|
||||
if (is_object($element->find('img', 0))) {
|
||||
$article_thumbnail = [ $element->find('img', 0)->src ];
|
||||
}
|
||||
|
||||
// Content (truncated)
|
||||
$article_content = $element->find('div.home-desc', 0)->plaintext;
|
||||
|
||||
// Now try expanding article
|
||||
$article_url = $element->find('a.story-link', 0)->href;
|
||||
$article = getSimpleHTMLDOMCached($article_url);
|
||||
if ($article) {
|
||||
//Article body
|
||||
$var = $article->find('div.articlebody', 0);
|
||||
if ($var) {
|
||||
$contents = $var->innertext;
|
||||
$contents = stripRecursiveHtmlSection($contents, 'div', '<div class="ad_');
|
||||
$contents = stripWithDelimiters($contents, 'id="google_ads', '</iframe>');
|
||||
$contents = stripWithDelimiters($contents, '<script', '</script>');
|
||||
$article_html = getSimpleHTMLDOMCached($article_url);
|
||||
if ($article_html) {
|
||||
// Content (expanded and cleaned)
|
||||
$article_body = $article_html->find('div.articlebody', 0);
|
||||
if ($article_body) {
|
||||
$article_body = convertLazyLoading($article_body);
|
||||
$article_body = defaultLinkTo($article_body, $article_url);
|
||||
$header_img = $article_body->find('img', 0);
|
||||
if ($header_img) {
|
||||
$header_img->parent->style = '';
|
||||
}
|
||||
foreach ($article_body->find('center.cf') as $center_ad) {
|
||||
$center_ad->outertext = '';
|
||||
}
|
||||
$article_content = $article_body->innertext;
|
||||
}
|
||||
//Date with time
|
||||
if (is_object($article->find('meta[itemprop=dateModified]', 0))) {
|
||||
$article_timestamp = strtotime(
|
||||
extractFromDelimiters(
|
||||
$article->find('meta[itemprop=dateModified]', 0)->outertext,
|
||||
"content='",
|
||||
"'"
|
||||
)
|
||||
);
|
||||
// Author
|
||||
$spans_author = $article_html->find('span.author');
|
||||
if (count($spans_author) > 0) {
|
||||
$article_author = $spans_author[array_key_last($spans_author)]->plaintext;
|
||||
}
|
||||
} else {
|
||||
$contents = 'Could not request TheHackerNews: ' . $article_url;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $article_url;
|
||||
$item['title'] = $article_title;
|
||||
if ($article_author) {
|
||||
if (!empty($article_author)) {
|
||||
$item['author'] = $article_author;
|
||||
}
|
||||
$item['enclosures'] = $article_thumbnail;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['content'] = trim($contents ?? '');
|
||||
$item['content'] = trim($article_content);
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
@@ -26,18 +26,6 @@ class TikTokBridge extends BridgeAbstract
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
if (preg_match('/tiktok\.com\/(@[\w]+)/', $url, $matches) > 0) {
|
||||
return [
|
||||
'context' => 'By user',
|
||||
'username' => $matches[1]
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
@@ -48,12 +36,24 @@ class TikTokBridge extends BridgeAbstract
|
||||
foreach ($html->find('div.tiktok-x6y88p-DivItemContainerV2') as $div) {
|
||||
$item = [];
|
||||
|
||||
// todo: find proper link to tiktok item
|
||||
$link = $div->find('a', 0)->href;
|
||||
$image = $div->find('img', 0)->src;
|
||||
|
||||
$image = $div->find('img', 0)->src ?? '';
|
||||
|
||||
$views = $div->find('strong.video-count', 0)->plaintext;
|
||||
|
||||
if ($link === 'https://www.tiktok.com/') {
|
||||
$link = $this->getURI();
|
||||
}
|
||||
$item['uri'] = $link;
|
||||
$item['title'] = $div->find('a', 1)->plaintext;
|
||||
|
||||
$a = $div->find('a', 1);
|
||||
if ($a) {
|
||||
$item['title'] = $a->plaintext;
|
||||
} else {
|
||||
$item['title'] = $this->getName();
|
||||
}
|
||||
$item['enclosures'][] = $image;
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
@@ -87,10 +87,25 @@ EOD;
|
||||
|
||||
private function processUsername()
|
||||
{
|
||||
if (substr($this->getInput('username'), 0, 1) !== '@') {
|
||||
return '@' . $this->getInput('username');
|
||||
$username = trim($this->getInput('username'));
|
||||
if (preg_match('#^https?://www\.tiktok\.com/@(.*)$#', $username, $m)) {
|
||||
return '@' . $m[1];
|
||||
}
|
||||
if (substr($username, 0, 1) !== '@') {
|
||||
return '@' . $username;
|
||||
}
|
||||
return $username;
|
||||
}
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
if (preg_match('/tiktok\.com\/(@[\w]+)/', $url, $matches) > 0) {
|
||||
return [
|
||||
'context' => 'By user',
|
||||
'username' => $matches[1]
|
||||
];
|
||||
}
|
||||
|
||||
return $this->getInput('username');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -223,10 +223,10 @@ EOD;
|
||||
CURLOPT_POSTFIELDS => json_encode($request)
|
||||
];
|
||||
|
||||
Debug::log("Sending GraphQL query:\n" . $query);
|
||||
Debug::log("Sending GraphQL variables:\n" . json_encode($variables, JSON_PRETTY_PRINT));
|
||||
Logger::debug("Sending GraphQL query:\n" . $query);
|
||||
Logger::debug("Sending GraphQL variables:\n" . json_encode($variables, JSON_PRETTY_PRINT));
|
||||
$response = json_decode(getContents('https://gql.twitch.tv/gql', $header, $opts));
|
||||
Debug::log("Got GraphQL response:\n" . json_encode($response, JSON_PRETTY_PRINT));
|
||||
Logger::debug("Got GraphQL response:\n" . json_encode($response, JSON_PRETTY_PRINT));
|
||||
|
||||
if (isset($response->errors)) {
|
||||
$messages = array_column($response->errors, 'message');
|
||||
|
@@ -123,7 +123,7 @@ EOD
|
||||
|
||||
private $apiKey = null;
|
||||
private $guestToken = null;
|
||||
private $authHeader = [];
|
||||
private $authHeaders = [];
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
@@ -219,25 +219,27 @@ EOD
|
||||
$tweets = [];
|
||||
|
||||
// Get authentication information
|
||||
$this->getApiKey();
|
||||
|
||||
// Try to get all tweets
|
||||
switch ($this->queriedContext) {
|
||||
case 'By username':
|
||||
$user = $this->makeApiCall('/1.1/users/show.json', ['screen_name' => $this->getInput('u')]);
|
||||
if (!$user) {
|
||||
returnServerError('Requested username can\'t be found.');
|
||||
}
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope('twitter');
|
||||
$cache->setKey(['cache']);
|
||||
// todo: inspect mtime instead of purging with 3h
|
||||
$cache->purgeCache(60 * 60 * 3);
|
||||
$api = new TwitterClient($cache);
|
||||
|
||||
$params = [
|
||||
'user_id' => $user->id_str,
|
||||
'tweet_mode' => 'extended'
|
||||
];
|
||||
$screenName = $this->getInput('u');
|
||||
$screenName = trim($screenName);
|
||||
$screenName = ltrim($screenName, '@');
|
||||
|
||||
$data = $api->fetchUserTweets($screenName);
|
||||
|
||||
$data = $this->makeApiCall('/1.1/statuses/user_timeline.json', $params);
|
||||
break;
|
||||
|
||||
case 'By keyword or hashtag':
|
||||
// Does not work with the recent twitter changes
|
||||
$params = [
|
||||
'q' => urlencode($this->getInput('q')),
|
||||
'tweet_mode' => 'extended',
|
||||
@@ -248,6 +250,7 @@ EOD
|
||||
break;
|
||||
|
||||
case 'By list':
|
||||
// Does not work with the recent twitter changes
|
||||
$params = [
|
||||
'slug' => strtolower($this->getInput('list')),
|
||||
'owner_screen_name' => strtolower($this->getInput('user')),
|
||||
@@ -258,6 +261,7 @@ EOD
|
||||
break;
|
||||
|
||||
case 'By list ID':
|
||||
// Does not work with the recent twitter changes
|
||||
$params = [
|
||||
'list_id' => $this->getInput('listid'),
|
||||
'tweet_mode' => 'extended',
|
||||
@@ -284,7 +288,10 @@ EOD
|
||||
}
|
||||
|
||||
// Filter out unwanted tweets
|
||||
foreach ($data as $tweet) {
|
||||
foreach ($data->tweets as $tweet) {
|
||||
if (!$tweet) {
|
||||
continue;
|
||||
}
|
||||
// Filter out retweets to remove possible duplicates of original tweet
|
||||
switch ($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
@@ -333,9 +340,9 @@ EOD
|
||||
$realtweet = $tweet->retweeted_status;
|
||||
}
|
||||
|
||||
$item['username'] = $realtweet->user->screen_name;
|
||||
$item['fullname'] = $realtweet->user->name;
|
||||
$item['avatar'] = $realtweet->user->profile_image_url_https;
|
||||
$item['username'] = $data->user_info->legacy->screen_name;
|
||||
$item['fullname'] = $data->user_info->legacy->name;
|
||||
$item['avatar'] = $data->user_info->legacy->profile_image_url_https;
|
||||
$item['timestamp'] = $realtweet->created_at;
|
||||
$item['id'] = $realtweet->id_str;
|
||||
$item['uri'] = self::URI . $item['username'] . '/status/' . $item['id'];
|
||||
@@ -503,9 +510,7 @@ EOD;
|
||||
//This function takes 2 requests, and therefore is cached
|
||||
private function getApiKey($forceNew = 0)
|
||||
{
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$r_cache = $cacheFactory->create();
|
||||
$r_cache = RssBridge::getCache();
|
||||
$scope = 'TwitterBridge';
|
||||
$r_cache->setScope($scope);
|
||||
$r_cache->setKey(['refresh']);
|
||||
@@ -521,7 +526,7 @@ EOD;
|
||||
|
||||
$cacheFactory = new CacheFactory();
|
||||
|
||||
$cache = $cacheFactory->create();
|
||||
$cache = RssBridge::getCache();
|
||||
$cache->setScope($scope);
|
||||
$cache->setKey(['api_key']);
|
||||
$data = $cache->loadData();
|
||||
@@ -556,9 +561,7 @@ EOD;
|
||||
$apiKey = $data;
|
||||
}
|
||||
|
||||
$cacheFac2 = new CacheFactory();
|
||||
|
||||
$gt_cache = $cacheFactory->create();
|
||||
$gt_cache = RssBridge::getCache();
|
||||
$gt_cache->setScope($scope);
|
||||
$gt_cache->setKey(['guest_token']);
|
||||
$guestTokenUses = $gt_cache->loadData();
|
||||
|
30
bridges/UsesTechBridge.php
Normal file
30
bridges/UsesTechBridge.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
class UsesTechbridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = '/uses';
|
||||
const URI = 'https://uses.tech/';
|
||||
const DESCRIPTION = 'RSS feed for /uses';
|
||||
const MAINTAINER = 'jummo4@yahoo.de';
|
||||
const MAX_ITEM = 100; # Maximum items to loop through which works fast enough on my computer
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request: ' . self::URI);
|
||||
|
||||
foreach ($html->find('div[class=PersonInner]') as $index => $a) {
|
||||
$item = []; // Create an empty item
|
||||
$articlePath = $a->find('a[class=displayLink]', 0)->href;
|
||||
$item['title'] = $a->find('img', 0)->getAttribute('alt');
|
||||
$item['author'] = $a->find('img', 0)->getAttribute('alt');
|
||||
$item['uri'] = $articlePath;
|
||||
$item['content'] = $a->find('p', 0)->innertext;
|
||||
|
||||
$this->items[] = $item; // Add item to the list
|
||||
if (count($this->items) >= self::MAX_ITEM) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
76
bridges/VideoCardzBridge.php
Normal file
76
bridges/VideoCardzBridge.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
class VideoCardzBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'VideoCardz';
|
||||
const URI = 'https://videocardz.com/';
|
||||
const DESCRIPTION = 'Returns news from VideoCardz.com';
|
||||
const MAINTAINER = 'rmscoelho';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'feed' => [
|
||||
'name' => 'News Feed',
|
||||
'type' => 'list',
|
||||
'title' => 'Feeds from VideoCardz.com',
|
||||
'values' => [
|
||||
'News' => 'sections/news',
|
||||
'Featured' => 'sections/featured',
|
||||
'Leaks' => 'sections/leaks',
|
||||
'Press Releases' => 'sections/press-releases',
|
||||
'Preview Roundup' => 'sections/review-roundup',
|
||||
'Rumour' => 'sections/rumor',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://videocardz.com/favicon-32x32.png?x66580';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return !is_null($this->getKey('feed')) ? self::NAME . ' | ' . $this->getKey('feed') : self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return self::URI . $this->getInput('feed');
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = sprintf('https://videocardz.com/%s', $this->getInput('feed'));
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
$dom = $dom->find('.subcategory-news', 0);
|
||||
if (!$dom) {
|
||||
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
|
||||
}
|
||||
$dom = defaultLinkTo($dom, $this->getURI());
|
||||
|
||||
foreach ($dom->find('article') as $article) {
|
||||
$title = preg_replace('/\(PR\) /i', '', $article->find('h2', 0)->plaintext);
|
||||
//Get thumbnail
|
||||
$image = $article->style;
|
||||
$image = preg_replace('/background-image:url\(/i', '', $image);
|
||||
$image = substr_replace($image, '', -3);
|
||||
//Get date and time of publishing
|
||||
$datetime = date_parse($article->find('.main-index-article-datetitle-date > a', 0)->plaintext);
|
||||
$year = $datetime['year'];
|
||||
$month = $datetime['month'];
|
||||
$day = $datetime['day'];
|
||||
$hour = $datetime['hour'];
|
||||
$minute = $datetime['minute'];
|
||||
$timestamp = mktime($hour, $minute, 0, $month, $day, $year);
|
||||
$content = '<img src="' . $image . '" alt="' . $article->find('h2', 0)->plaintext . ' thumbnail" />';
|
||||
$this->items[] = [
|
||||
'title' => $title,
|
||||
'uri' => $article->find('p.main-index-article-datetitle-date > a', 0)->href,
|
||||
'content' => $content,
|
||||
'timestamp' => $timestamp,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,8 +22,18 @@ class VkBridge extends BridgeAbstract
|
||||
]
|
||||
]
|
||||
];
|
||||
const TEST_DETECT_PARAMETERS = [
|
||||
'https://vk.com/id1' => ['u' => 'id1'],
|
||||
'https://vk.com/groupname' => ['u' => 'groupname'],
|
||||
'https://m.vk.com/groupname' => ['u' => 'groupname'],
|
||||
'https://vk.com/groupname/anythingelse' => ['u' => 'groupname'],
|
||||
'https://vk.com/groupname?w=somethingelse' => ['u' => 'groupname'],
|
||||
'https://vk.com/with_underscore' => ['u' => 'with_underscore'],
|
||||
];
|
||||
|
||||
protected $pageName;
|
||||
protected $tz = 0;
|
||||
private $urlRegex = '/vk\.com\/([\w]+)/';
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
@@ -43,6 +53,15 @@ class VkBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
if (preg_match($this->urlRegex, $url, $matches)) {
|
||||
return ['u' => $matches[1]];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$text_html = $this->getContents();
|
||||
@@ -50,6 +69,13 @@ class VkBridge extends BridgeAbstract
|
||||
$text_html = iconv('windows-1251', 'utf-8//ignore', $text_html);
|
||||
|
||||
$html = str_get_html($text_html);
|
||||
foreach ($html->find('script') as $script) {
|
||||
preg_match('/tz: ([0-9]+)/', $script->outertext, $matches);
|
||||
if (count($matches) > 0) {
|
||||
$this->tz = intval($matches[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$pageName = $html->find('.page_name', 0);
|
||||
if (is_object($pageName)) {
|
||||
$pageName = $pageName->plaintext;
|
||||
@@ -285,6 +311,44 @@ class VkBridge extends BridgeAbstract
|
||||
$copy_quote->outertext = "<br>Reposted ($copy_quote_author): <br>$copy_quote_content";
|
||||
}
|
||||
|
||||
foreach ($post->find('.SecondaryAttachment') as $sa) {
|
||||
$sa_href = $sa->getAttribute('href');
|
||||
if (!$sa_href) {
|
||||
$sa_href = '';
|
||||
}
|
||||
$sa_task_click = $sa->getAttribute('data-task-click');
|
||||
|
||||
if (str_starts_with($sa_href, 'https://vk.com/doc')) {
|
||||
// document
|
||||
$doc_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext;
|
||||
$doc_size = $sa->find('.SecondaryAttachmentSubhead', 0)->innertext;
|
||||
$doc_link = $sa_href;
|
||||
$content_suffix .= "<br>Doc: <a href='$doc_link'>$doc_title</a> ($doc_size)";
|
||||
$sa->outertext = '';
|
||||
} else if (str_starts_with($sa_href, 'https://vk.com/@')) {
|
||||
// article
|
||||
$article_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext;
|
||||
$article_author = explode('Article · from ', $sa->find('.SecondaryAttachmentSubhead', 0)->innertext)[1];
|
||||
$article_link = $sa_href;
|
||||
$content_suffix .= "<br>Article: <a href='$article_link'>$article_title ($article_author)</a>";
|
||||
$sa->outertext = '';
|
||||
} else if ($sa_task_click == 'SecondaryAttachment/playAudio') {
|
||||
// audio
|
||||
$audio_json = json_decode(html_entity_decode($sa->getAttribute('data-audio')));
|
||||
$audio_link = $audio_json->url;
|
||||
$audio_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext;
|
||||
$audio_author = $sa->find('.SecondaryAttachmentSubhead', 0)->innertext;
|
||||
$content_suffix .= "<br>Audio: <a href='$audio_link'>$audio_title ($audio_author)</a>";
|
||||
$sa->outertext = '';
|
||||
} else if ($sa_task_click == 'SecondaryAttachment/playPlaylist') {
|
||||
// playlist link
|
||||
$playlist_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext;
|
||||
$playlist_link = $sa->find('.SecondaryAttachment__link', 0)->getAttribute('href');
|
||||
$content_suffix .= "<br>Playlist: <a href='$playlist_link'>$playlist_title</a>";
|
||||
$sa->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$content = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '<a><br><img>');
|
||||
$content .= $content_suffix;
|
||||
@@ -393,8 +457,9 @@ class VkBridge extends BridgeAbstract
|
||||
|
||||
private function getTime($post)
|
||||
{
|
||||
if ($time = $post->find('time.PostHeaderSubtitle__item', 0)->getAttribute('time')) {
|
||||
return $time;
|
||||
$accurateDateElement = $post->find('span.rel_date', 0);
|
||||
if ($accurateDateElement) {
|
||||
return $accurateDateElement->getAttribute('time');
|
||||
} else {
|
||||
$strdate = $post->find('time.PostHeaderSubtitle__item', 0)->plaintext;
|
||||
$strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate);
|
||||
@@ -417,7 +482,7 @@ class VkBridge extends BridgeAbstract
|
||||
$date['hour'] = $date['minute'] = '00';
|
||||
}
|
||||
return strtotime($date['day'] . '-' . $date['month'] . '-' . $date['year'] . ' ' .
|
||||
$date['hour'] . ':' . $date['minute']);
|
||||
$date['hour'] . ':' . $date['minute']) - $this->tz;
|
||||
}
|
||||
}
|
||||
|
||||
|
27
bridges/WYMTNewsBridge.php
Normal file
27
bridges/WYMTNewsBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
class WYMTNewsBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'WYMT Mountain News';
|
||||
const URI = 'https://www.wymt.com/news/';
|
||||
const DESCRIPTION = 'Returns the recent articles published on WYMT Mountain News (Hazard KY)';
|
||||
const MAINTAINER = 'mattconnell';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$articles = $html->find('.card-body');
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$item = [];
|
||||
$url = $article->find('.headline a', 0);
|
||||
$item['uri'] = $url->href;
|
||||
$item['title'] = trim($url->plaintext);
|
||||
$item['author'] = $article->find('.author', 0)->plaintext;
|
||||
$item['content'] = $article->find('.deck', 0)->plaintext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -117,7 +117,7 @@ The default URI shows the Madara demo page.';
|
||||
protected function getMangaInfo($url)
|
||||
{
|
||||
$url_cache = 'TitleInfo_' . preg_replace('/[^\w]/', '.', rtrim($url, '/'));
|
||||
$cache = $this->loadCacheValue($url_cache);
|
||||
$cache = $this->loadCacheValue($url_cache, 86400);
|
||||
if (isset($cache)) {
|
||||
return $cache;
|
||||
}
|
||||
|
@@ -39,8 +39,13 @@ class YandexZenBridge extends BridgeAbstract
|
||||
|
||||
$item['uri'] = $post->share_link;
|
||||
$item['title'] = $post->title;
|
||||
$item['timestamp'] = date(DateTimeInterface::ATOM, $post->publication_date);
|
||||
$item['content'] = $post->text;
|
||||
|
||||
$publicationDateUnixTimestamp = $post->publication_date ?? null;
|
||||
if ($publicationDateUnixTimestamp) {
|
||||
$item['timestamp'] = date(DateTimeInterface::ATOM, $publicationDateUnixTimestamp);
|
||||
}
|
||||
|
||||
$item['content'] = $post->text . "<br /><img src='$post->image' />";
|
||||
$item['enclosures'] = [
|
||||
$post->image,
|
||||
];
|
||||
|
@@ -78,7 +78,7 @@ class YouTubeCommunityTabBridge extends BridgeAbstract
|
||||
returnServerError('Channel does not have a community tab');
|
||||
}
|
||||
|
||||
foreach ($this->getCommunityPosts($json) as $post) {
|
||||
foreach ($this->getCommunityPosts($json) as $key => $post) {
|
||||
$this->itemTitle = '';
|
||||
|
||||
if (!isset($post->backstagePostThreadRenderer)) {
|
||||
@@ -102,14 +102,20 @@ class YouTubeCommunityTabBridge extends BridgeAbstract
|
||||
$item['content'] .= $this->getAttachments($details);
|
||||
$item['title'] = $this->itemTitle;
|
||||
|
||||
$date = strtotime(str_replace(' (edited)', '', $details->publishedTimeText->runs[0]->text));
|
||||
if (is_int($date)) {
|
||||
// subtract an increasing multiple of 60 seconds to always preserve the original order
|
||||
$item['timestamp'] = $date - $key * 60;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if (!empty($this->feedUri)) {
|
||||
return $this->feedUri;
|
||||
if (!empty($this->feedUrl)) {
|
||||
return $this->feedUrl;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
@@ -13,7 +13,6 @@ class YoutubeBridge extends BridgeAbstract
|
||||
const URI = 'https://www.youtube.com/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the 10 newest videos by username/channel/playlist or search';
|
||||
const MAINTAINER = 'em92';
|
||||
|
||||
const PARAMETERS = [
|
||||
'By username' => [
|
||||
@@ -234,7 +233,11 @@ class YoutubeBridge extends BridgeAbstract
|
||||
private function getJSONData($html)
|
||||
{
|
||||
$scriptRegex = '/var ytInitialData = (.*?);<\/script>/';
|
||||
preg_match($scriptRegex, $html, $matches) or returnServerError('Could not find ytInitialData');
|
||||
$result = preg_match($scriptRegex, $html, $matches);
|
||||
if (! $result) {
|
||||
Logger::debug('Could not find ytInitialData');
|
||||
return null;
|
||||
}
|
||||
return json_decode($matches[1]);
|
||||
}
|
||||
|
||||
@@ -293,15 +296,17 @@ class YoutubeBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('/([\d]{1,2})\:([\d]{1,2})\:([\d]{2})/', $durationText)) {
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{1,2})\:([\d]{2})/', '$1:$2:$3', $durationText);
|
||||
} else {
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText);
|
||||
}
|
||||
sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds);
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
if ($duration < $duration_min || $duration > $duration_max) {
|
||||
continue;
|
||||
if (is_string($durationText)) {
|
||||
if (preg_match('/([\d]{1,2})\:([\d]{1,2})\:([\d]{2})/', $durationText)) {
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{1,2})\:([\d]{2})/', '$1:$2:$3', $durationText);
|
||||
} else {
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText);
|
||||
}
|
||||
sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds);
|
||||
$duration = $hours * 3600 + $minutes * 60 + $seconds;
|
||||
if ($duration < $duration_min || $duration > $duration_max) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// $vid_list .= $vid . ',';
|
||||
@@ -336,6 +341,7 @@ class YoutubeBridge extends BridgeAbstract
|
||||
$html = $this->ytGetSimpleHTMLDOM($url_listing);
|
||||
$jsonData = $this->getJSONData($html);
|
||||
$url_feed = $jsonData->metadata->channelMetadataRenderer->rssUrl;
|
||||
$this->iconURL = $jsonData->metadata->channelMetadataRenderer->avatar->thumbnails[0]->url;
|
||||
}
|
||||
if (!$this->skipFeeds()) {
|
||||
$html = $this->ytGetSimpleHTMLDOM($url_feed);
|
||||
@@ -444,4 +450,13 @@ class YoutubeBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
if (empty($this->iconURL)) {
|
||||
return parent::getIcon();
|
||||
} else {
|
||||
return $this->iconURL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,8 +66,10 @@ class ZeitBridge extends FeedExpander
|
||||
$item['enclosures'] = [];
|
||||
|
||||
$headers = [
|
||||
'User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
|
||||
'X-Forwarded-For: 66.249.66.1',
|
||||
'Cookie: zonconsent=' . date('Y-m-d\TH:i:s.v\Z'),
|
||||
'User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'];
|
||||
];
|
||||
|
||||
// one-page article
|
||||
$article = getSimpleHTMLDOM($item['uri'], $headers);
|
||||
|
@@ -3,42 +3,56 @@
|
||||
class FileCache implements CacheInterface
|
||||
{
|
||||
private array $config;
|
||||
protected $scope;
|
||||
protected $key;
|
||||
protected string $scope;
|
||||
protected string $key;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config;
|
||||
$default = [
|
||||
'path' => null,
|
||||
'enable_purge' => true,
|
||||
];
|
||||
$this->config = array_merge($default, $config);
|
||||
if (!$this->config['path']) {
|
||||
throw new \Exception('The FileCache needs a path value');
|
||||
}
|
||||
// Normalize with a single trailing slash
|
||||
$this->config['path'] = rtrim($this->config['path'], '/') . '/';
|
||||
}
|
||||
|
||||
if (!is_dir($this->config['path'])) {
|
||||
throw new \Exception('The cache path does not exists. You probably want: mkdir cache && chown www-data:www-data cache');
|
||||
}
|
||||
if (!is_writable($this->config['path'])) {
|
||||
throw new \Exception('The cache path is not writeable. You probably want: chown www-data:www-data cache');
|
||||
}
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function loadData()
|
||||
{
|
||||
if (file_exists($this->getCacheFile())) {
|
||||
return unserialize(file_get_contents($this->getCacheFile()));
|
||||
if (!file_exists($this->getCacheFile())) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
$data = unserialize(file_get_contents($this->getCacheFile()));
|
||||
if ($data === false) {
|
||||
// Intentionally not throwing an exception
|
||||
Logger::warning(sprintf('Failed to unserialize: %s', $this->getCacheFile()));
|
||||
return null;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function saveData($data)
|
||||
public function saveData($data): void
|
||||
{
|
||||
$writeStream = file_put_contents($this->getCacheFile(), serialize($data));
|
||||
if ($writeStream === false) {
|
||||
throw new \Exception('The cache path is not writeable. You probably want: chown www-data:www-data cache');
|
||||
$bytes = file_put_contents($this->getCacheFile(), serialize($data), LOCK_EX);
|
||||
if ($bytes === false) {
|
||||
throw new \Exception(sprintf('Failed to write to: %s', $this->getCacheFile()));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
public function getTime(): ?int
|
||||
{
|
||||
// https://www.php.net/manual/en/function.clearstatcache.php
|
||||
clearstatcache();
|
||||
|
||||
$cacheFile = $this->getCacheFile();
|
||||
clearstatcache(false, $cacheFile);
|
||||
if (file_exists($cacheFile)) {
|
||||
$time = filemtime($cacheFile);
|
||||
if ($time !== false) {
|
||||
@@ -50,7 +64,7 @@ class FileCache implements CacheInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function purgeCache($seconds)
|
||||
public function purgeCache(int $seconds): void
|
||||
{
|
||||
if (! $this->config['enable_purge']) {
|
||||
return;
|
||||
@@ -66,38 +80,32 @@ class FileCache implements CacheInterface
|
||||
);
|
||||
|
||||
foreach ($cacheIterator as $cacheFile) {
|
||||
if (in_array($cacheFile->getBasename(), ['.', '..', '.gitkeep'])) {
|
||||
$basename = $cacheFile->getBasename();
|
||||
$excluded = [
|
||||
'.' => true,
|
||||
'..' => true,
|
||||
'.gitkeep' => true,
|
||||
];
|
||||
if (isset($excluded[$basename])) {
|
||||
continue;
|
||||
} elseif ($cacheFile->isFile()) {
|
||||
if (filemtime($cacheFile->getPathname()) < time() - $seconds) {
|
||||
$filepath = $cacheFile->getPathname();
|
||||
if (filemtime($filepath) < time() - $seconds) {
|
||||
// todo: sometimes this file doesn't exists
|
||||
unlink($cacheFile->getPathname());
|
||||
unlink($filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setScope($scope)
|
||||
public function setScope(string $scope): void
|
||||
{
|
||||
if (!is_string($scope)) {
|
||||
throw new \Exception('The given scope is invalid!');
|
||||
}
|
||||
|
||||
$this->scope = $this->config['path'] . trim($scope, " \t\n\r\0\x0B\\\/") . '/';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setKey($key)
|
||||
public function setKey(array $key): void
|
||||
{
|
||||
$key = json_encode($key);
|
||||
|
||||
if (!is_string($key)) {
|
||||
throw new \Exception('The given key is invalid!');
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
$this->key = json_encode($key);
|
||||
}
|
||||
|
||||
private function getScope()
|
||||
|
@@ -2,11 +2,11 @@
|
||||
|
||||
class MemcachedCache implements CacheInterface
|
||||
{
|
||||
private $scope;
|
||||
private $key;
|
||||
private string $scope;
|
||||
private string $key;
|
||||
private $conn;
|
||||
private $expiration = 0;
|
||||
private $time = false;
|
||||
private $time = null;
|
||||
private $data = null;
|
||||
|
||||
public function __construct()
|
||||
@@ -58,11 +58,11 @@ class MemcachedCache implements CacheInterface
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
public function saveData($datas)
|
||||
public function saveData($data): void
|
||||
{
|
||||
$time = time();
|
||||
$object_to_save = [
|
||||
'data' => $datas,
|
||||
'data' => $data,
|
||||
'time' => $time,
|
||||
];
|
||||
$result = $this->conn->set($this->getCacheKey(), $object_to_save, $this->expiration);
|
||||
@@ -72,44 +72,31 @@ class MemcachedCache implements CacheInterface
|
||||
}
|
||||
|
||||
$this->time = $time;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
public function getTime(): ?int
|
||||
{
|
||||
if ($this->time === false) {
|
||||
if ($this->time === null) {
|
||||
$this->loadData();
|
||||
}
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
public function purgeCache($duration)
|
||||
public function purgeCache(int $seconds): void
|
||||
{
|
||||
// Note: does not purges cache right now
|
||||
// Just sets cache expiration and leave cache purging for memcached itself
|
||||
$this->expiration = $duration;
|
||||
$this->expiration = $seconds;
|
||||
}
|
||||
|
||||
public function setScope($scope)
|
||||
public function setScope(string $scope): void
|
||||
{
|
||||
$this->scope = $scope;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setKey($key)
|
||||
public function setKey(array $key): void
|
||||
{
|
||||
if (!empty($key) && is_array($key)) {
|
||||
$key = array_map('strtolower', $key);
|
||||
}
|
||||
$key = json_encode($key);
|
||||
|
||||
if (!is_string($key)) {
|
||||
throw new \Exception('The given key is invalid!');
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
$this->key = json_encode($key);
|
||||
}
|
||||
|
||||
private function getCacheKey()
|
||||
|
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
class NullCache implements CacheInterface
|
||||
{
|
||||
public function setScope($scope)
|
||||
public function setScope(string $scope): void
|
||||
{
|
||||
}
|
||||
|
||||
public function setKey($key)
|
||||
public function setKey(array $key): void
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,15 +16,16 @@ class NullCache implements CacheInterface
|
||||
{
|
||||
}
|
||||
|
||||
public function saveData($data)
|
||||
public function saveData($data): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
public function getTime(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function purgeCache($seconds)
|
||||
public function purgeCache(int $seconds): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -5,51 +5,43 @@
|
||||
*/
|
||||
class SQLiteCache implements CacheInterface
|
||||
{
|
||||
protected $scope;
|
||||
protected $key;
|
||||
private \SQLite3 $db;
|
||||
private string $scope;
|
||||
private string $key;
|
||||
private array $config;
|
||||
|
||||
private $db = null;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!extension_loaded('sqlite3')) {
|
||||
throw new \Exception('"sqlite3" extension not loaded. Please check "php.ini"');
|
||||
$default = [
|
||||
'file' => null,
|
||||
'timeout' => 5000,
|
||||
'enable_purge' => true,
|
||||
];
|
||||
$config = array_merge($default, $config);
|
||||
$this->config = $config;
|
||||
|
||||
if (!$config['file']) {
|
||||
throw new \Exception('sqlite cache needs a file');
|
||||
}
|
||||
|
||||
if (!is_writable(PATH_CACHE)) {
|
||||
throw new \Exception('The cache folder is not writable');
|
||||
}
|
||||
|
||||
$section = 'SQLiteCache';
|
||||
$file = Configuration::getConfig($section, 'file');
|
||||
if (!$file) {
|
||||
throw new \Exception(sprintf('Configuration for %s missing.', $section));
|
||||
}
|
||||
|
||||
if (dirname($file) == '.') {
|
||||
$file = PATH_CACHE . $file;
|
||||
} elseif (!is_dir(dirname($file))) {
|
||||
throw new \Exception(sprintf('Invalid configuration for %s', $section));
|
||||
}
|
||||
|
||||
if (!is_file($file)) {
|
||||
// The instantiation creates the file
|
||||
$this->db = new \SQLite3($file);
|
||||
if (is_file($config['file'])) {
|
||||
$this->db = new \SQLite3($config['file']);
|
||||
$this->db->enableExceptions(true);
|
||||
} else {
|
||||
// Create the file and create sql schema
|
||||
$this->db = new \SQLite3($config['file']);
|
||||
$this->db->enableExceptions(true);
|
||||
$this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)");
|
||||
} else {
|
||||
$this->db = new \SQLite3($file);
|
||||
$this->db->enableExceptions(true);
|
||||
}
|
||||
$this->db->busyTimeout(5000);
|
||||
$this->db->busyTimeout($config['timeout']);
|
||||
}
|
||||
|
||||
public function loadData()
|
||||
{
|
||||
$Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
|
||||
$Qselect->bindValue(':key', $this->getCacheKey());
|
||||
$result = $Qselect->execute();
|
||||
if ($result instanceof \SQLite3Result) {
|
||||
$stmt = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
|
||||
$stmt->bindValue(':key', $this->getCacheKey());
|
||||
$result = $stmt->execute();
|
||||
if ($result) {
|
||||
$data = $result->fetchArray(\SQLITE3_ASSOC);
|
||||
if (isset($data['value'])) {
|
||||
return unserialize($data['value']);
|
||||
@@ -59,24 +51,22 @@ class SQLiteCache implements CacheInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function saveData($data)
|
||||
public function saveData($data): void
|
||||
{
|
||||
$Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
|
||||
$Qupdate->bindValue(':key', $this->getCacheKey());
|
||||
$Qupdate->bindValue(':value', serialize($data));
|
||||
$Qupdate->bindValue(':updated', time());
|
||||
$Qupdate->execute();
|
||||
|
||||
return $this;
|
||||
$stmt = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
|
||||
$stmt->bindValue(':key', $this->getCacheKey());
|
||||
$stmt->bindValue(':value', serialize($data));
|
||||
$stmt->bindValue(':updated', time());
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
public function getTime(): ?int
|
||||
{
|
||||
$Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
|
||||
$Qselect->bindValue(':key', $this->getCacheKey());
|
||||
$result = $Qselect->execute();
|
||||
if ($result instanceof \SQLite3Result) {
|
||||
$data = $result->fetchArray(SQLITE3_ASSOC);
|
||||
$stmt = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
|
||||
$stmt->bindValue(':key', $this->getCacheKey());
|
||||
$result = $stmt->execute();
|
||||
if ($result) {
|
||||
$data = $result->fetchArray(\SQLITE3_ASSOC);
|
||||
if (isset($data['updated'])) {
|
||||
return $data['updated'];
|
||||
}
|
||||
@@ -85,44 +75,28 @@ class SQLiteCache implements CacheInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function purgeCache($seconds)
|
||||
public function purgeCache(int $seconds): void
|
||||
{
|
||||
$Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
|
||||
$Qdelete->bindValue(':expired', time() - $seconds);
|
||||
$Qdelete->execute();
|
||||
if (!$this->config['enable_purge']) {
|
||||
return;
|
||||
}
|
||||
$stmt = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
|
||||
$stmt->bindValue(':expired', time() - $seconds);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function setScope($scope)
|
||||
public function setScope(string $scope): void
|
||||
{
|
||||
if (is_null($scope) || !is_string($scope)) {
|
||||
throw new \Exception('The given scope is invalid!');
|
||||
}
|
||||
|
||||
$this->scope = $scope;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setKey($key)
|
||||
public function setKey(array $key): void
|
||||
{
|
||||
if (!empty($key) && is_array($key)) {
|
||||
$key = array_map('strtolower', $key);
|
||||
}
|
||||
$key = json_encode($key);
|
||||
|
||||
if (!is_string($key)) {
|
||||
throw new \Exception('The given key is invalid!');
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
$this->key = json_encode($key);
|
||||
}
|
||||
|
||||
private function getCacheKey()
|
||||
{
|
||||
if (is_null($this->key)) {
|
||||
throw new \Exception('Call "setKey" first!');
|
||||
}
|
||||
|
||||
return hash('sha1', $this->scope . $this->key, true);
|
||||
}
|
||||
}
|
||||
|
557
composer.lock
generated
557
composer.lock
generated
@@ -4,35 +4,35 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0d00dfa7c120bdd750ac0ac4a45f5452",
|
||||
"content-hash": "24083060ddb8be9a95e75f6596e3bb83",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.4.1",
|
||||
"version": "1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/instantiator.git",
|
||||
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
|
||||
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
|
||||
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
|
||||
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
|
||||
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9",
|
||||
"doctrine/coding-standard": "^9 || ^11",
|
||||
"ext-pdo": "*",
|
||||
"ext-phar": "*",
|
||||
"phpbench/phpbench": "^0.16 || ^1",
|
||||
"phpstan/phpstan": "^1.4",
|
||||
"phpstan/phpstan-phpunit": "^1",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"vimeo/psalm": "^4.22"
|
||||
"vimeo/psalm": "^4.30 || ^5.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -59,7 +59,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/instantiator/issues",
|
||||
"source": "https://github.com/doctrine/instantiator/tree/1.4.1"
|
||||
"source": "https://github.com/doctrine/instantiator/tree/1.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -75,20 +75,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-03T08:28:38+00:00"
|
||||
"time": "2022-12-30T00:15:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
|
||||
"reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
|
||||
"reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -126,7 +126,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -134,20 +134,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-03T13:19:32+00:00"
|
||||
"time": "2023-03-08T13:26:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "210577fe3cf7badcc5814d99455df46564f3c077"
|
||||
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
|
||||
"reference": "210577fe3cf7badcc5814d99455df46564f3c077",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
|
||||
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -188,9 +188,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
|
||||
},
|
||||
"time": "2021-11-30T19:35:32+00:00"
|
||||
"time": "2023-06-25T14:52:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@@ -303,252 +303,25 @@
|
||||
},
|
||||
"time": "2022-02-21T01:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
||||
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
|
||||
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-2.x": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\Reflection\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jaap van Otterdijk",
|
||||
"email": "opensource@ijaap.nl"
|
||||
}
|
||||
],
|
||||
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
|
||||
"homepage": "http://www.phpdoc.org",
|
||||
"keywords": [
|
||||
"FQSEN",
|
||||
"phpDocumentor",
|
||||
"phpdoc",
|
||||
"reflection",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
|
||||
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
|
||||
},
|
||||
"time": "2020-06-27T09:03:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "5.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
|
||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-filter": "*",
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpdocumentor/reflection-common": "^2.2",
|
||||
"phpdocumentor/type-resolver": "^1.3",
|
||||
"webmozart/assert": "^1.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1.3.2",
|
||||
"psalm/phar": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\Reflection\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mike van Riel",
|
||||
"email": "me@mikevanriel.com"
|
||||
},
|
||||
{
|
||||
"name": "Jaap van Otterdijk",
|
||||
"email": "account@ijaap.nl"
|
||||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
|
||||
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
|
||||
},
|
||||
"time": "2021-10-19T17:43:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "77a32518733312af16a44300404e945338981de3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
|
||||
"reference": "77a32518733312af16a44300404e945338981de3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpdocumentor/reflection-common": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-tokenizer": "*",
|
||||
"psalm/phar": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-1.x": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\Reflection\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mike van Riel",
|
||||
"email": "me@mikevanriel.com"
|
||||
}
|
||||
],
|
||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
|
||||
},
|
||||
"time": "2022-03-15T21:29:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "v1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
||||
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.2",
|
||||
"php": "^7.2 || ~8.0, <8.2",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"sebastian/comparator": "^3.0 || ^4.0",
|
||||
"sebastian/recursion-context": "^3.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpspec/phpspec": "^6.0 || ^7.0",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Prophecy\\": "src/Prophecy"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Konstantin Kudryashov",
|
||||
"email": "ever.zet@gmail.com",
|
||||
"homepage": "http://everzet.com"
|
||||
},
|
||||
{
|
||||
"name": "Marcello Duarte",
|
||||
"email": "marcello.duarte@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Highly opinionated mocking framework for PHP 5.3+",
|
||||
"homepage": "https://github.com/phpspec/prophecy",
|
||||
"keywords": [
|
||||
"Double",
|
||||
"Dummy",
|
||||
"fake",
|
||||
"mock",
|
||||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpspec/prophecy/issues",
|
||||
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
|
||||
},
|
||||
"time": "2021-12-08T12:19:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.15",
|
||||
"version": "9.2.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
|
||||
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
|
||||
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"nikic/php-parser": "^4.13.0",
|
||||
"nikic/php-parser": "^4.15",
|
||||
"php": ">=7.3",
|
||||
"phpunit/php-file-iterator": "^3.0.3",
|
||||
"phpunit/php-text-template": "^2.0.2",
|
||||
@@ -563,8 +336,8 @@
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcov": "*",
|
||||
"ext-xdebug": "*"
|
||||
"ext-pcov": "PHP extension that provides line coverage",
|
||||
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -597,7 +370,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -605,7 +378,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-07T09:28:20+00:00"
|
||||
"time": "2023-03-06T12:58:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@@ -850,20 +623,20 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.5.20",
|
||||
"version": "9.6.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
|
||||
"reference": "a9aceaf20a682aeacf28d582654a1670d8826778"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
|
||||
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778",
|
||||
"reference": "a9aceaf20a682aeacf28d582654a1670d8826778",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.3.1",
|
||||
"doctrine/instantiator": "^1.3.1 || ^2",
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
@@ -874,7 +647,6 @@
|
||||
"phar-io/manifest": "^2.0.3",
|
||||
"phar-io/version": "^3.0.2",
|
||||
"php": ">=7.3",
|
||||
"phpspec/prophecy": "^1.12.1",
|
||||
"phpunit/php-code-coverage": "^9.2.13",
|
||||
"phpunit/php-file-iterator": "^3.0.5",
|
||||
"phpunit/php-invoker": "^3.1.1",
|
||||
@@ -882,23 +654,19 @@
|
||||
"phpunit/php-timer": "^5.0.2",
|
||||
"sebastian/cli-parser": "^1.0.1",
|
||||
"sebastian/code-unit": "^1.0.6",
|
||||
"sebastian/comparator": "^4.0.5",
|
||||
"sebastian/comparator": "^4.0.8",
|
||||
"sebastian/diff": "^4.0.3",
|
||||
"sebastian/environment": "^5.1.3",
|
||||
"sebastian/exporter": "^4.0.3",
|
||||
"sebastian/exporter": "^4.0.5",
|
||||
"sebastian/global-state": "^5.0.1",
|
||||
"sebastian/object-enumerator": "^4.0.3",
|
||||
"sebastian/resource-operations": "^3.0.3",
|
||||
"sebastian/type": "^3.0",
|
||||
"sebastian/type": "^3.2",
|
||||
"sebastian/version": "^3.0.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pdo": "*",
|
||||
"phpspec/prophecy-phpunit": "^2.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-soap": "*",
|
||||
"ext-xdebug": "*"
|
||||
"ext-soap": "To be able to generate mocks based on WSDL files",
|
||||
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
|
||||
},
|
||||
"bin": [
|
||||
"phpunit"
|
||||
@@ -906,7 +674,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.5-dev"
|
||||
"dev-master": "9.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -937,7 +705,8 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -947,9 +716,13 @@
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-01T12:37:26+00:00"
|
||||
"time": "2023-06-11T06:13:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
@@ -1120,16 +893,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/comparator",
|
||||
"version": "4.0.6",
|
||||
"version": "4.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/comparator.git",
|
||||
"reference": "55f4261989e546dc112258c7a75935a81a7ce382"
|
||||
"reference": "fa0f136dd2334583309d32b62544682ee972b51a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
|
||||
"reference": "55f4261989e546dc112258c7a75935a81a7ce382",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
|
||||
"reference": "fa0f136dd2334583309d32b62544682ee972b51a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1182,7 +955,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/comparator/issues",
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1190,7 +963,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-26T15:49:45+00:00"
|
||||
"time": "2022-09-14T12:41:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/complexity",
|
||||
@@ -1251,16 +1024,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
"version": "4.0.4",
|
||||
"version": "4.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
|
||||
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
|
||||
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
|
||||
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1305,7 +1078,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1313,20 +1086,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-26T13:10:38+00:00"
|
||||
"time": "2023-05-07T05:35:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/environment",
|
||||
"version": "5.1.4",
|
||||
"version": "5.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/environment.git",
|
||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
|
||||
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
|
||||
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1368,7 +1141,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1376,20 +1149,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-03T09:37:03+00:00"
|
||||
"time": "2023-02-03T06:03:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/exporter",
|
||||
"version": "4.0.4",
|
||||
"version": "4.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/exporter.git",
|
||||
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
|
||||
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
|
||||
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
|
||||
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1445,7 +1218,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/exporter/issues",
|
||||
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
|
||||
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1453,7 +1226,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-11-11T14:18:36+00:00"
|
||||
"time": "2022-09-14T06:03:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/global-state",
|
||||
@@ -1690,16 +1463,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/recursion-context",
|
||||
"version": "4.0.4",
|
||||
"version": "4.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/recursion-context.git",
|
||||
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
|
||||
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
|
||||
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
|
||||
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1738,10 +1511,10 @@
|
||||
}
|
||||
],
|
||||
"description": "Provides functionality to recursively process PHP variables",
|
||||
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
|
||||
"homepage": "https://github.com/sebastianbergmann/recursion-context",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
|
||||
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
|
||||
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1749,7 +1522,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-26T13:17:30+00:00"
|
||||
"time": "2023-02-03T06:07:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/resource-operations",
|
||||
@@ -1808,16 +1581,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/type",
|
||||
"version": "3.0.0",
|
||||
"version": "3.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/type.git",
|
||||
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
|
||||
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
|
||||
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1829,7 +1602,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "3.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1852,7 +1625,7 @@
|
||||
"homepage": "https://github.com/sebastianbergmann/type",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/type/issues",
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1860,7 +1633,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-15T09:54:48+00:00"
|
||||
"time": "2023-02-03T06:13:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/version",
|
||||
@@ -1917,16 +1690,16 @@
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.6.2",
|
||||
"version": "3.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "5e4e71592f69da17871dba6e80dd51bce74a351a"
|
||||
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a",
|
||||
"reference": "5e4e71592f69da17871dba6e80dd51bce74a351a",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
|
||||
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1962,96 +1735,15 @@
|
||||
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"standards"
|
||||
"standards",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2021-12-12T21:44:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.25.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
|
||||
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-10-20T20:35:02+00:00"
|
||||
"time": "2023-02-22T23:07:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
@@ -2102,64 +1794,6 @@
|
||||
}
|
||||
],
|
||||
"time": "2021-07-28T10:34:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<0.12.20",
|
||||
"vimeo/psalm": "<4.6.1 || 4.6.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.13"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.10-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webmozart\\Assert\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Assertions to validate method input/output with nice error messages.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"check",
|
||||
"validate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/1.10.0"
|
||||
},
|
||||
"time": "2021-03-09T10:59:23+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
@@ -2174,8 +1808,9 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-json": "*"
|
||||
"ext-json": "*",
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
@@ -6,6 +6,26 @@
|
||||
|
||||
[system]
|
||||
|
||||
; Only these bridges are available for feed production
|
||||
; How to enable all bridges: enabled_bridges[] = *
|
||||
enabled_bridges[] = FeedMerge
|
||||
enabled_bridges[] = FeedReducerBridge
|
||||
enabled_bridges[] = Filter
|
||||
enabled_bridges[] = GettrBridge
|
||||
enabled_bridges[] = MastodonBridge
|
||||
enabled_bridges[] = Reddit
|
||||
enabled_bridges[] = RumbleBridge
|
||||
enabled_bridges[] = SoundcloudBridge
|
||||
enabled_bridges[] = Telegram
|
||||
enabled_bridges[] = ThePirateBay
|
||||
enabled_bridges[] = TikTokBridge
|
||||
enabled_bridges[] = Twitch
|
||||
enabled_bridges[] = Twitter
|
||||
enabled_bridges[] = Vk
|
||||
enabled_bridges[] = XPathBridge
|
||||
enabled_bridges[] = Youtube
|
||||
enabled_bridges[] = YouTubeCommunityTabBridge
|
||||
|
||||
; Defines the timezone used by RSS-Bridge
|
||||
; Find a list of supported timezones at
|
||||
; https://www.php.net/manual/en/timezones.php
|
||||
@@ -15,6 +35,16 @@ timezone = "UTC"
|
||||
; Display a system message to users.
|
||||
message = ""
|
||||
|
||||
; Whether to enable debug mode.
|
||||
enable_debug_mode = false
|
||||
|
||||
; Enable debug mode only for these permitted ip addresses
|
||||
; debug_mode_whitelist[] = 127.0.0.1
|
||||
; debug_mode_whitelist[] = 192.168.1.10
|
||||
|
||||
; Whether to enable maintenance mode. If enabled, feed requests receive 503 Service Unavailable
|
||||
enable_maintenance_mode = false
|
||||
|
||||
[http]
|
||||
timeout = 60
|
||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"
|
||||
@@ -103,7 +133,12 @@ path = ""
|
||||
enable_purge = true
|
||||
|
||||
[SQLiteCache]
|
||||
; Filepath of the sqlite db file
|
||||
file = "cache.sqlite"
|
||||
; Whether to actually delete data when purging
|
||||
enable_purge = true
|
||||
; Busy wait in ms before timing out
|
||||
timeout = 5000
|
||||
|
||||
[MemcachedCache]
|
||||
host = "localhost"
|
||||
|
@@ -2,8 +2,8 @@ server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
root /app;
|
||||
access_log /var/log/nginx/rssbridge.access.log;
|
||||
error_log /var/log/nginx/rssbridge.error.log;
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stderr;
|
||||
index index.php;
|
||||
|
||||
location ~ /(\.|vendor|tests) {
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|  | https://rss-bridge.mediani.de |  | [@sokai](https://github.com/sokai) | Hosted with Netcup, Germany |
|
||||
|  | http://rb.vern.cc/ |  | [@vern.cc](https://vern.cc/en/admin) | Hosted with Hetzner, US |
|
||||
|  | https://rssbridge.flossboxin.org.in/ |  | [@vdbhb59](https://github.com/vdbhb59) | Hosted with OVH SAS (Maintained in India)
|
||||
|  | https://rss.foxhaven.cyou|  | [@Aysilu](https://foxhaven.cyou) | Hosted with Timeweb (Maintained in Poland)
|
||||
|
||||
## Inactive instances
|
||||
|
||||
|
@@ -45,5 +45,5 @@ services:
|
||||
If you want to add a bridge that is not part of [`/bridges`](https://github.com/RSS-Bridge/rss-bridge/tree/master/bridges), you can map a folder to the `/config` folder of the `rss-bridge` container.
|
||||
|
||||
1. Create a folder in the location of your docker-compose.yml or your general docker working area (in this example it will be `/home/docker/rssbridge/config` ).
|
||||
2. Copy your [custom bridges](../05_Bridge_API/01_How_to_create_a_new_bridge.md) to the `/home/docker/rssbridge/config` folder. You can also add your custom [whitelist.txt](../03_For_Hosts/05_Whitelisting.md) file and your custom [config.ini.php](../03_For_Hosts/08_Custom_Configuration.md) to this folder.
|
||||
2. Copy your [custom bridges](../05_Bridge_API/01_How_to_create_a_new_bridge.md) to the `/home/docker/rssbridge/config` folder. Applies also to [config.ini.php](../03_For_Hosts/08_Custom_Configuration.md).
|
||||
3. Map the folder to `/config` inside the container. To do that, replace the `</local/custom/path>` from the previous examples with `/home/docker/rssbridge/config`
|
@@ -14,7 +14,7 @@ You can simply press the button below to easily deploy RSS Bridge on Heroku and
|
||||
|
||||

|
||||
|
||||
2. To customise what bridges can be used if need, create a `whitelist.txt` file in your fork and follow the instructions given [here](../03_For_Hosts/05_Whitelisting.md). You don’t need to do this if you’re fine with the default bridges.
|
||||
2. To customise what bridges can be used if need, see [here](../03_For_Hosts/05_Whitelisting.md). You don’t need to do this if you’re fine with the default bridges.
|
||||
|
||||
3. [Log in to Heroku](https://dashboard.heroku.com) and create a new app. The app name will be the URL of the RSS Bridge (appname.herokuapp.com)
|
||||
|
||||
|
@@ -1,38 +1,26 @@
|
||||
RSS-Bridge supports whitelists in order to limit the available bridges on your web server.
|
||||
Modify `config.ini.php` to limit available bridges.
|
||||
|
||||
A default whitelist file (`whitelist.default.txt`) is shipped with RSS-Bridge. Please do not edit this file, as it gets replaced when upgrading RSS-Bridge!
|
||||
## Enable all bridges
|
||||
|
||||
You should, however, use this file as template to create your own whitelist (or leave it as is, to keep the default bridges). In order to create your own whitelist perform following actions:
|
||||
|
||||
* Copy the file `whitelist.default.txt` in the RSS-Bridge root folder
|
||||
* Rename the new file to `whitelist.txt`
|
||||
* Change the lines to satisfy your requirements
|
||||
|
||||
RSS-Bridge will automatically detect the `whitelist.txt` and use it. If the file doesn't exist it will default to `whitelist.default.txt` automatically.
|
||||
|
||||
# Specific whitelisting
|
||||
|
||||
In order to specifically whitelist bridges, open `whitelist.txt` and add one line for each bridge you want to show. Make sure you use normal [line-feeds](https://en.wikipedia.org/wiki/Newline "Line-feed") at the end of a line (LF not [CRLF](https://en.wikipedia.org/wiki/Carriage_return "Carriage-return line-feed")). The bridge name must match the filename of the bridge in the bridges folder (see [folder structure](../04_For_Developers/03_Folder_structure.md)). The name may or may not include the 'Bridge' part.
|
||||
|
||||
**Examples**:
|
||||
```TEXT
|
||||
FacebookBridge
|
||||
WikipediaBridge
|
||||
TwitterBridge
|
||||
```
|
||||
enabled_bridges[] = *
|
||||
```
|
||||
|
||||
or
|
||||
## Enable some bridges
|
||||
|
||||
```TEXT
|
||||
Facebook
|
||||
Wikipedia
|
||||
Twitter
|
||||
```
|
||||
enabled_bridges[] = TwitchBridge
|
||||
enabled_bridges[] = GettrBridge
|
||||
```
|
||||
|
||||
# Global whitelisting
|
||||
## Enable all bridges (legacy shortcut)
|
||||
|
||||
In order to globally whitelist all bridges, open the `whitelist.txt` file, remove all contents and just write an asterisk `*` into the file (only this one character).
|
||||
```
|
||||
echo '*' > whitelist.txt
|
||||
```
|
||||
|
||||
```TEXT
|
||||
*
|
||||
```
|
||||
## Enable some bridges (legacy shortcut)
|
||||
|
||||
```
|
||||
echo -e "TwitchBridge\nTwitterBridge" > whitelist.txt
|
||||
```
|
||||
|
@@ -5,23 +5,29 @@ Enabling debug mode on a public server may result in malicious clients retrievin
|
||||
***
|
||||
|
||||
Debug mode enables error reporting and prevents loading data from the cache (data is still written to the cache).
|
||||
To enable debug mode, create a file named 'DEBUG' in the root directory of RSS-Bridge (next to `index.php`). For further security, insert your IP address in the file. You can add multiple addresses, one per line.
|
||||
To enable debug mode, set in `config.ini.php`:
|
||||
|
||||
enable_debug_mode = true
|
||||
|
||||
Allow only explicit ip addresses:
|
||||
|
||||
debug_mode_whitelist[] = 127.0.0.1
|
||||
debug_mode_whitelist[] = 192.168.1.10
|
||||
|
||||
_Notice_:
|
||||
|
||||
* An empty file enables debug mode for anyone!
|
||||
* The bridge whitelist still applies! (debug mode does **not** enable all bridges)
|
||||
|
||||
RSS-Bridge will give you a visual feedback when debug mode is enabled:
|
||||
|
||||

|
||||
RSS-Bridge will give you a visual feedback when debug mode is enabled.
|
||||
|
||||
While debug mode is active, RSS-Bridge will write additional data to your servers `error.log`.
|
||||
|
||||
Debug mode is controlled by the static class `Debug`. It provides three core functions:
|
||||
|
||||
`Debug::isEnabled()`: Returns `true` if debug mode is enabled.
|
||||
`Debug::isSecure()`: Returns `true` if your client is on the debug whitelist.
|
||||
`Debug::log($message)`: Adds a message to `error.log`. It takes one parameter, which can be anything. For example: `Debug::log('Hello World!');`
|
||||
* `Debug::isEnabled()`: Returns `true` if debug mode is enabled.
|
||||
* `Debug::log($message)`: Adds a message to `error.log`. It takes one parameter, which can be anything.
|
||||
|
||||
Example: `Debug::log('Hello World!');`
|
||||
|
||||
**Notice**: `Debug::log($message)` calls `Debug::isEnabled()` internally. You don't have to do that manually.
|
@@ -489,11 +489,11 @@ public function collectData()
|
||||
Within the context of the current bridge, loads a value by key from cache. Optionally specifies the cache duration for the key. Returns `null` if the key doesn't exist or the value is expired.
|
||||
|
||||
```php
|
||||
protected function loadCacheValue($key, $duration = 86400)
|
||||
protected function loadCacheValue($key, $duration = null)
|
||||
```
|
||||
|
||||
- `$key` - the name under which the value is stored in the cache.
|
||||
- `$duration` - the maximum time in seconds after which the value expires. The default duration is 86400 (24 hours).
|
||||
- `$duration` - the maximum time in seconds after which the value expires.
|
||||
|
||||
Usage example:
|
||||
|
||||
|
@@ -53,7 +53,7 @@ $html = getContents($url, $header, $opts);
|
||||
```
|
||||
|
||||
# getSimpleHTMLDOM
|
||||
The `getSimpleHTMLDOM` function is a wrapper for the [simple_html_dom](http://simplehtmldom.sourceforge.net/) [file_get_html](http://simplehtmldom.sourceforge.net/manual_api.htm#api) function in order to provide context by design.
|
||||
The `getSimpleHTMLDOM` function is a wrapper for the [simple_html_dom](https://simplehtmldom.sourceforge.io/) [file_get_html](https://simplehtmldom.sourceforge.io/docs/1.9/api/file_get_html/) function in order to provide context by design.
|
||||
|
||||
```PHP
|
||||
$html = getSimpleHTMLDOM('your URI');
|
||||
@@ -194,8 +194,20 @@ $cleaned = stripRecursiveHTMLSection($string, $tag_name, $tag_start);
|
||||
# markdownToHtml
|
||||
Converts markdown input to HTML using [Parsedown](https://parsedown.org/).
|
||||
|
||||
| Parameter | Type | Optional | Description
|
||||
| --------- | ------ | ---------- | ----------
|
||||
| `string` | string | *required* | The URL of the contents to acquire
|
||||
| `config` | array | *optional* | An array of Parsedown options in the format `['breaksEnabled' => true]`
|
||||
|
||||
Valid options:
|
||||
| Option | Default | Description
|
||||
| --------------- | ------- | -----------
|
||||
| `breaksEnabled` | `false` | Enable automatic line breaks
|
||||
| `markupEscaped` | `false` | Escape inline markup (HTML)
|
||||
| `urlsLinked` | `true` | Automatically convert URLs to links
|
||||
|
||||
```php
|
||||
function markdownToHtml(string $string) : string
|
||||
function markdownToHtml(string $string, array $config = []) : string
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
@@ -1,24 +1,3 @@
|
||||
Create a new file in the `caches/` folder (see [Folder structure](../04_For_Developers/03_Folder_structure.md)).
|
||||
|
||||
The file must be named according to following specification:
|
||||
|
||||
* It starts with the type
|
||||
* The file name must end with 'Cache'
|
||||
* The file type must be PHP, written in small letters (seriously!) ".php"
|
||||
|
||||
**Examples:**
|
||||
|
||||
Type | Filename
|
||||
-----|---------
|
||||
File | FileCache.php
|
||||
MySQL | MySQLCache.php
|
||||
|
||||
The file must start with the PHP tags and end with an empty line. The closing tag `?>` is [omitted](http://php.net/basic-syntax.instruction-separation).
|
||||
|
||||
Example:
|
||||
|
||||
```PHP
|
||||
<?PHP
|
||||
// PHP code here
|
||||
// This line is empty (just imagine it!)
|
||||
```
|
||||
See `NullCache` and `SQLiteCache` for examples.
|
@@ -1,73 +1,18 @@
|
||||
The `CacheInterface` interface defines functions that need to be implemented. To create a new cache that implements `CacheInterface` you must implement following functions:
|
||||
See `CacheInterface`.
|
||||
|
||||
* [loadData](#the-loaddata-function)
|
||||
* [saveData](#the-savedata-function)
|
||||
* [getTime](#the-gettime-function)
|
||||
* [purgeCache](#the-purgecache-function)
|
||||
```php
|
||||
interface CacheInterface
|
||||
{
|
||||
public function setScope(string $scope): void;
|
||||
|
||||
Find a [template](#template) at the end of this file.
|
||||
public function setKey(array $key): void;
|
||||
|
||||
# Functions
|
||||
public function loadData();
|
||||
|
||||
## The `loadData` function
|
||||
public function saveData($data): void;
|
||||
|
||||
This function loads data from the cache and returns the data in the same format provided to the [saveData](#the-savedata-function) function.
|
||||
public function getTime(): ?int;
|
||||
|
||||
```PHP
|
||||
loadData(): mixed
|
||||
```
|
||||
|
||||
## The `saveData` function
|
||||
|
||||
This function stores the given data into the cache and returns the object instance.
|
||||
|
||||
```PHP
|
||||
saveData(mixed $data): self
|
||||
```
|
||||
|
||||
## The `getTime` function
|
||||
|
||||
This function returns the last write time for the cache, or `false` if the cache does not yet exist. Please notice that 'cache' refers to one specific item in the cache repository and might require additional data to identify a specific item (introduce custom functions where necessary!).
|
||||
|
||||
```PHP
|
||||
getTime(): int, false
|
||||
```
|
||||
|
||||
## The `purgeCache` function
|
||||
|
||||
This function removes any data from the cache that is not within the given duration. The duration is specified in seconds and defines the period between now and the oldest item to keep.
|
||||
|
||||
```PHP
|
||||
purgeCache(int $duration): null
|
||||
```
|
||||
|
||||
# Template
|
||||
|
||||
This is the bare minimum template for a new cache:
|
||||
|
||||
```PHP
|
||||
<?php
|
||||
class MyTypeCache implements CacheInterface {
|
||||
public function loadData(){
|
||||
// Implement your algorithm here!
|
||||
return null;
|
||||
}
|
||||
|
||||
public function saveData($data){
|
||||
// Implement your algorithm here!
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTime(){
|
||||
// Implement your algorithm here!
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function purgeCache($duration){
|
||||
// Implement your algorithm here!
|
||||
}
|
||||
public function purgeCache(int $seconds): void;
|
||||
}
|
||||
// Imaginary empty line!
|
||||
```
|
@@ -1,13 +0,0 @@
|
||||
The `FormatAbstract` class implements the [`FormatInterface`](../08_Format_API/02_FormatInterface.md) interface with basic functional behavior and adds common helper functions for new formats:
|
||||
|
||||
* [sanitizeHtml](#the-sanitizehtml-function)
|
||||
|
||||
# Functions
|
||||
|
||||
## The `sanitizeHtml` function
|
||||
|
||||
The `sanitizeHtml` function receives an HTML formatted string and returns the string with disabled `<script>`, `<iframe>` and `<link>` tags.
|
||||
|
||||
```PHP
|
||||
sanitize_html(string $html): string
|
||||
```
|
@@ -1,3 +1,9 @@
|
||||
A _Format_ is an class that allows **RSS-Bridge** to turn items from a bridge into an RSS-feed format. It is developed in a PHP file located in the `formats/` folder (see [Folder structure](../04_For_Developers/03_Folder_structure.md)) and either implements the [FormatInterface](../08_Format_API/02_FormatInterface.md) interface or extends the [FormatAbstract](../08_Format_API/03_FormatAbstract.md) class.
|
||||
A Format is a class that allows RSS-Bridge to turn items from a bridge into an RSS-feed format.
|
||||
It is developed in a PHP file located in the `formats/` folder
|
||||
[Folder structure](../04_For_Developers/03_Folder_structure.md)
|
||||
and either implements the
|
||||
[FormatInterface](../08_Format_API/02_FormatInterface.md)
|
||||
interface or extends the FormatAbstract class.
|
||||
|
||||
For more information about how to create a new _Format_, read [How to create a new Format?](./01_How_to_create_a_new_format.md)
|
||||
For more information about how to create a new _Format_, read
|
||||
[How to create a new Format?](./01_How_to_create_a_new_format.md)
|
@@ -12,7 +12,7 @@ Configuration
|
||||
|
||||
- I will not detail exactly how to do this, as the specific process will likely change over time. You should easily be able to find guides using your search engine of choice.
|
||||
|
||||
- A basic free developer account grants Essential access to the Twitter API v2, which should be sufficient for this bridge.
|
||||
- Note: as of April 2023, the "Free" access level no longer allows read access. The cheapest access level with read access is called "Basic".
|
||||
|
||||
2. Create a Twitter Project and App, get Bearer Token
|
||||
|
||||
@@ -34,4 +34,4 @@ Configuration
|
||||
[TwitterV2Bridge]
|
||||
twitterv2apitoken = %Bearer Token from step 2%
|
||||
```
|
||||
- If you don't have a **config.ini.php**, create one by making a copy of **config.default.ini.php**
|
||||
- If you don't have a **config.ini.php**, create one by making a copy of **config.default.ini.php**
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
@@ -158,7 +158,7 @@ class AtomFormat extends FormatAbstract
|
||||
|
||||
$content = $document->createElement('content');
|
||||
$content->setAttribute('type', 'html');
|
||||
$content->appendChild($document->createTextNode(sanitize_html($entryContent)));
|
||||
$content->appendChild($document->createTextNode(break_annoying_html_tags($entryContent)));
|
||||
$entry->appendChild($content);
|
||||
|
||||
foreach ($item->getEnclosures() as $enclosure) {
|
||||
|
@@ -47,7 +47,7 @@ class JsonFormat extends FormatAbstract
|
||||
$entryTitle = $item->getTitle();
|
||||
$entryUri = $item->getURI();
|
||||
$entryTimestamp = $item->getTimestamp();
|
||||
$entryContent = $item->getContent() ? sanitize_html($item->getContent()) : '';
|
||||
$entryContent = $item->getContent() ? break_annoying_html_tags($item->getContent()) : '';
|
||||
$entryEnclosures = $item->getEnclosures();
|
||||
$entryCategories = $item->getCategories();
|
||||
|
||||
|
@@ -103,7 +103,7 @@ class MrssFormat extends FormatAbstract
|
||||
$itemTimestamp = $item->getTimestamp();
|
||||
$itemTitle = $item->getTitle();
|
||||
$itemUri = $item->getURI();
|
||||
$itemContent = $item->getContent() ? sanitize_html($item->getContent()) : '';
|
||||
$itemContent = $item->getContent() ? break_annoying_html_tags($item->getContent()) : '';
|
||||
$entryID = $item->getUid();
|
||||
$isPermaLink = 'false';
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user