1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-17 14:00:43 +02:00

Compare commits

..

2 Commits

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

View File

@@ -1,61 +0,0 @@
name: Build Image on Commit and Release
on:
push:
branches:
- 'master'
tags:
- '20*'
env:
DOCKERHUB_SLUG: rssbridge/rss-bridge
GHCR_SLUG: ghcr.io/rss-bridge/rss-bridge
jobs:
bake:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.4
-
name: Docker meta
id: docker_meta
uses: docker/metadata-action@v3.5.0
with:
images: |
${{ env.DOCKERHUB_SLUG }}
${{ env.GHCR_SLUG }}
tags: |
type=raw,value=latest
type=sha
type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.6.0
-
name: Login to DockerHub
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
uses: docker/bake-action@v1.6.0
with:
files: |
./docker-bake.hcl
${{ steps.docker_meta.outputs.bake-file }}
targets: image-all
push: true

View File

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

View File

@@ -1,35 +0,0 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
# TODO: return back when fixed https://github.com/RSS-Bridge/rss-bridge/issues/2391
# phpunit7:
# runs-on: ubuntu-20.04
# strategy:
# matrix:
# php-versions: ['7.1', '7.2', '7.3']
# steps:
# - uses: actions/checkout@v2
# - uses: shivammathur/setup-php@v2
# with:
# php-version: ${{ matrix.php-versions }}
# - run: composer global require phpunit/phpunit ^7
# - run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit8:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.3', '7.4', '8.0', '8.1']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^8
- run: phpunit --configuration=phpunit.xml --include-path=lib/

46
.travis.yml Normal file
View File

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

View File

@@ -1,9 +1,5 @@
FROM php:7-apache
LABEL description="RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one."
LABEL repository="https://github.com/RSS-Bridge/rss-bridge"
LABEL website="https://github.com/RSS-Bridge/rss-bridge"
ENV APACHE_DOCUMENT_ROOT=/app
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
@@ -11,7 +7,6 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& apt-get --yes --no-install-recommends install \
zlib1g-dev \
libmemcached-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
@@ -19,6 +14,4 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& sed -ri -e 's/(MinProtocol\s*=\s*)TLSv1\.2/\1None/' /etc/ssl/openssl.cnf \
&& sed -ri -e 's/(CipherString\s*=\s*DEFAULT)@SECLEVEL=2/\1/' /etc/ssl/openssl.cnf
COPY --chown=www-data:www-data ./ /app/
CMD ["/app/docker-entrypoint.sh"]
COPY --chown=www-data:www-data ./ /app/

View File

@@ -1,15 +1,10 @@
![RSS-Bridge](static/logo_600px.png)
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE)
[![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest)
[![irc.libera.chat](https://img.shields.io/badge/irc.libera.chat-%23rssbridge-blue.svg)](https://web.libera.chat/#rssbridge)
[![Chat on Matrix](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#rssbridge:libera.chat)
[![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions)
[![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
====
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
**Important**: RSS-Bridge is __not__ aa feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
Supported sites/pages (examples)
===
@@ -18,11 +13,11 @@ Supported sites/pages (examples)
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/2047) for public instances)
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
* `GoogleSearch` : Most recent results from Google Search
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
* `Instagram`: Most recent photos from an Instagram user (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/1891) for public instances)
* `Instagram`: Most recent photos from an Instagram user
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
* `Pinterest`: Most recent photos from user or search
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
@@ -62,7 +57,7 @@ RSS-Bridge hashtag (#rss-bridge) search on Twitter, in Atom format (as displayed
Requirements
===
RSS-Bridge requires PHP 7.1 or higher with following extensions enabled:
RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
@@ -70,7 +65,6 @@ RSS-Bridge requires PHP 7.1 or higher with following extensions enabled:
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php)
- [`filter`](https://secure.php.net/manual/en/book.filter.php)
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
@@ -113,45 +107,36 @@ We are RSS-Bridge community, a group of developers continuing the project initia
**Contributors** (sorted alphabetically):
<!--
Use this script to generate the list automatically (using the GitHub API):
./contrib/prepare_release/fetch_contributors.php
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
* [86423355844265459587182778](https://github.com/86423355844265459587182778)
* [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [akirk](https://github.com/akirk)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
* [alex73](https://github.com/alex73)
* [alexAubin](https://github.com/alexAubin)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [arnd-s](https://github.com/arnd-s)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
* [AxorPL](https://github.com/AxorPL)
* [ayacoo](https://github.com/ayacoo)
* [az5he6ch](https://github.com/az5he6ch)
* [azdkj532](https://github.com/azdkj532)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [Binnette](https://github.com/Binnette)
* [Bockiii](https://github.com/Bockiii)
* [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel)
* [Chouchen](https://github.com/Chouchen)
* [ckiw](https://github.com/ckiw)
* [cn-tools](https://github.com/cn-tools)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [csisoap](https://github.com/csisoap)
* [cyberjacob](https://github.com/cyberjacob)
* [da2x](https://github.com/da2x)
* [dabenzel](https://github.com/dabenzel)
* [Daiyousei](https://github.com/Daiyousei)
* [dawidsowa](https://github.com/dawidsowa)
* [DevonHess](https://github.com/DevonHess)
* [dhuschde](https://github.com/dhuschde)
* [disk0x](https://github.com/disk0x)
* [DJCrashdummy](https://github.com/DJCrashdummy)
* [Djuuu](https://github.com/Djuuu)
@@ -159,53 +144,32 @@ Use this script to generate the list automatically (using the GitHub API):
* [dominik-th](https://github.com/dominik-th)
* [Draeli](https://github.com/Draeli)
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
* [drego85](https://github.com/drego85)
* [drklee3](https://github.com/drklee3)
* [dvikan](https://github.com/dvikan)
* [em92](https://github.com/em92)
* [eMerzh](https://github.com/eMerzh)
* [EtienneM](https://github.com/EtienneM)
* [f0086](https://github.com/f0086)
* [fanch317](https://github.com/fanch317)
* [fatuuse](https://github.com/fatuuse)
* [fivefilters](https://github.com/fivefilters)
* [floviolleau](https://github.com/floviolleau)
* [fluffy-critter](https://github.com/fluffy-critter)
* [fmachen](https://github.com/fmachen)
* [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
* [ggiessen](https://github.com/ggiessen)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [girlpunk](https://github.com/girlpunk)
* [Glandos](https://github.com/Glandos)
* [gloony](https://github.com/gloony)
* [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [gsantner](https://github.com/gsantner)
* [guigot](https://github.com/guigot)
* [hollowleviathan](https://github.com/hollowleviathan)
* [hpacleb](https://github.com/hpacleb)
* [hunhejj](https://github.com/hunhejj)
* [husim0](https://github.com/husim0)
* [IceWreck](https://github.com/IceWreck)
* [j0k3r](https://github.com/j0k3r)
* [JackNUMBER](https://github.com/JackNUMBER)
* [jacquesh](https://github.com/jacquesh)
* [JasonGhent](https://github.com/JasonGhent)
* [jcgoette](https://github.com/jcgoette)
* [jdesgats](https://github.com/jdesgats)
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [JimDog546](https://github.com/JimDog546)
* [jNullj](https://github.com/jNullj)
* [Jocker666z](https://github.com/Jocker666z)
* [johnnygroovy](https://github.com/johnnygroovy)
* [johnpc](https://github.com/johnpc)
* [joni1993](https://github.com/joni1993)
* [KamaleiZestri](https://github.com/KamaleiZestri)
* [killruana](https://github.com/killruana)
* [klimplant](https://github.com/klimplant)
* [kolarcz](https://github.com/kolarcz)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [l1n](https://github.com/l1n)
@@ -214,95 +178,62 @@ Use this script to generate the list automatically (using the GitHub API):
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [Leomaradan](https://github.com/Leomaradan)
* [leyrer](https://github.com/leyrer)
* [liamka](https://github.com/liamka)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [lukasklinger](https://github.com/lukasklinger)
* [m0zes](https://github.com/m0zes)
* [Mar-Koeh](https://github.com/Mar-Koeh)
* [marcus-at-localhost](https://github.com/marcus-at-localhost)
* [marius851000](https://github.com/marius851000)
* [matthewseal](https://github.com/matthewseal)
* [mcbyte-it](https://github.com/mcbyte-it)
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [metaMMA](https://github.com/metaMMA)
* [mibe](https://github.com/mibe)
* [mightymt](https://github.com/mightymt)
* [mitsukarenai](https://github.com/mitsukarenai)
* [Monocularity](https://github.com/Monocularity)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mschwld](https://github.com/mschwld)
* [mxmehl](https://github.com/mxmehl)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [Niehztog](https://github.com/Niehztog)
* [Nono-m0le](https://github.com/Nono-m0le)
* [obsiwitch](https://github.com/obsiwitch)
* [Ololbu](https://github.com/Ololbu)
* [ObsidianWitch](https://github.com/ObsidianWitch)
* [OliverParoczai](https://github.com/OliverParoczai)
* [oratosquilla-oratoria](https://github.com/oratosquilla-oratoria)
* [ORelio](https://github.com/ORelio)
* [otakuf](https://github.com/otakuf)
* [Park0](https://github.com/Park0)
* [Paroleen](https://github.com/Paroleen)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
* [PeterDaveHello](https://github.com/PeterDaveHello)
* [Peterr-K](https://github.com/Peterr-K)
* [Piranhaplant](https://github.com/Piranhaplant)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [Pofilo](https://github.com/Pofilo)
* [prysme01](https://github.com/prysme01)
* [Qluxzz](https://github.com/Qluxzz)
* [quentinus95](https://github.com/quentinus95)
* [rakoo](https://github.com/rakoo)
* [RawkBob](https://github.com/RawkBob)
* [regisenguehard](https://github.com/regisenguehard)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [ronansalmon](https://github.com/ronansalmon)
* [rremizov](https://github.com/rremizov)
* [sal0max](https://github.com/sal0max)
* [sebsauvage](https://github.com/sebsauvage)
* [shutosg](https://github.com/shutosg)
* [simon816](https://github.com/simon816)
* [Simounet](https://github.com/Simounet)
* [somini](https://github.com/somini)
* [SpangleLabs](https://github.com/SpangleLabs)
* [squeek502](https://github.com/squeek502)
* [stjohnjohnson](https://github.com/stjohnjohnson)
* [Stopka](https://github.com/Stopka)
* [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sunchaserinfo](https://github.com/sunchaserinfo)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [sysadminstory](https://github.com/sysadminstory)
* [t0stiman](https://github.com/t0stiman)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [tgkenney](https://github.com/tgkenney)
* [thefranke](https://github.com/thefranke)
* [ThePadawan](https://github.com/ThePadawan)
* [TheRadialActive](https://github.com/TheRadialActive)
* [theScrabi](https://github.com/theScrabi)
* [thezeroalpha](https://github.com/thezeroalpha)
* [timendum](https://github.com/timendum)
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
* [triatic](https://github.com/triatic)
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [xurxof](https://github.com/xurxof)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [yue-dongchen](https://github.com/yue-dongchen)
* [ZeNairolf](https://github.com/ZeNairolf)
Licenses
@@ -334,6 +265,6 @@ You're not social when you hamper sharing by removing feeds. You're happy to hav
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We are rebuilding bridges you have willfully destroyed.
We are rebuilding bridges you have wilfully destroyed.
Get your shit together: Put RSS/Atom back in.

View File

@@ -131,7 +131,6 @@ class DisplayAction extends ActionAbstract {
try {
$bridge->setDatas($bridge_params);
$bridge->loadConfiguration();
$bridge->collectData();
$items = $bridge->getItems();
@@ -151,7 +150,6 @@ class DisplayAction extends ActionAbstract {
$infos = array(
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'icon' => $bridge->getIcon()
);
} catch(Error $e) {

View File

@@ -39,7 +39,6 @@ class ListAction extends ActionAbstract {
$list->bridges[$bridgeName] = array(
'status' => $status,
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),

View File

@@ -1,45 +0,0 @@
<?php
class ABCNewsBridge extends BridgeAbstract {
const NAME = 'ABC News Bridge';
const URI = 'https://www.abc.net.au';
const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation';
const MAINTAINER = 'yue-dongchen';
const PARAMETERS = array(
array(
'topic' => array(
'type' => 'list',
'name' => 'Region',
'title' => 'Choose state',
'values' => array(
'ACT' => 'act',
'NSW' => 'nsw',
'NT' => 'nt',
'QLD' => 'qld',
'SA' => 'sa',
'TAS' => 'tas',
'VIC' => 'vic',
'WA' => 'wa'
),
)
)
);
public function collectData() {
$url = 'https://www.abc.net.au/news/' . $this->getInput('topic');
$html = getSimpleHTMLDOM($url)->find('.YAJzu._26IxR._2kxNB._3BZxh', 0);
$html = defaultLinkTo($html, $this->getURI());
foreach($html->find('._2H7Su') as $article) {
$item = array();
$title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0);
$item['title'] = $title->plaintext;
$item['uri'] = $title->href;
$item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext;
$item['timestamp'] = strtotime($article->find('time', 0)->datetime);
$this->items[] = $item;
}
}
}

View File

@@ -37,7 +37,8 @@ class AO3Bridge extends BridgeAbstract {
// Feed for lists of works (e.g. recent works, search results, filtered tags,
// bookmarks, series, collections).
private function collectList($url) {
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('could not request AO3');
$html = defaultLinkTo($html, self::URI);
foreach($html->find('.index.group > li') as $element) {
@@ -64,7 +65,8 @@ class AO3Bridge extends BridgeAbstract {
// Feed for recent chapters of a specific work.
private function collectWork($id) {
$url = self::URI . "/works/$id/navigate";
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('could not request AO3');
$html = defaultLinkTo($html, self::URI);
$this->title = $html->find('h2 a', 0)->plaintext;

View File

@@ -1,91 +0,0 @@
<?php
class ARDMediathekBridge extends BridgeAbstract {
const NAME = 'ARD-Mediathek Bridge';
const URI = 'https://www.ardmediathek.de';
const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path';
const MAINTAINER = 'yue-dongchen';
/*
* Number of Items to be requested from ARDmediathek API
* 12 has been observed on the wild
* 29 is the highest successfully tested value
* More Items could be fetched via pagination
* The JSON-field pagination holds more information on that
* @const PAGESIZE number of requested items
*/
const PAGESIZE = 29;
/*
* The URL Prefix of the (Webapp-)API
* @const APIENDPOINT https-URL of the used endpoint
*/
const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/';
/*
* The URL prefix of the video link
* URLs from the webapp include a slug containing titles of show, episode, and tv station.
* It seems to work without that.
* @const VIDEOLINKPREFIX https-URL prefix of video links
*/
const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/';
/*
* The requested width of the preview image
* 432 has been observed on the wild
* The webapp seems to also compute and add the height value
* It seems to works without that.
* @const IMAGEWIDTH width in px of the preview image
*/
const IMAGEWIDTH = 432;
/*
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
* @const IMAGEWIDTHPLACEHOLDER
*/
const IMAGEWIDTHPLACEHOLDER = '{width}';
const PARAMETERS = array(
array(
'path' => array(
'name' => 'Show Link or ID',
'required' => true,
'title' => 'Link to the show page or just its alphanumeric suffix',
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
)
)
);
public function collectData() {
date_default_timezone_set('Europe/Berlin');
$pathComponents = explode('/', $this->getInput('path'));
if (empty($pathComponents)) {
returnClientError('Path may not be empty');
}
if (count($pathComponents) < 2) {
$showID = $pathComponents[0];
} else {
$lastKey = count($pathComponents) - 1;
$showID = $pathComponents[$lastKey];
if (strlen($showID) === 0) {
$showID = $pathComponents[$lastKey - 1];
}
}
$url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE;
$rawJSON = getContents($url);
$processedJSON = json_decode($rawJSON);
foreach($processedJSON->teasers as $video) {
$item = array();
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
$item['uri'] = SELF::VIDEOLINKPREFIX . $video->id . '/';
// there is also ->mediumTitle and ->shortTitle
$item['title'] = $video->longTitle;
// in the test, aspect16x9 was the only child of images, not sure whether that is always true
$item['enclosures'] = array(
str_replace(SELF::IMAGEWIDTHPLACEHOLDER, SELF::IMAGEWIDTH, $video->images->aspect16x9->src)
);
$item['content'] = '<img src="' . $item['enclosures'][0] . '" /><p>';
$item['timestamp'] = $video->broadcastedOn;
$item['uid'] = $video->id;
$item['author'] = $video->publicationService->name;
$this->items[] = $item;
}
}
}

View File

@@ -10,7 +10,8 @@ class ASRockNewsBridge extends BridgeAbstract {
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp');
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp')
or returnServerError('Could not request: ' . self::URI . '/news/index.asp');
$html = defaultLinkTo($html, self::URI . '/news/');
@@ -19,16 +20,17 @@ class ASRockNewsBridge extends BridgeAbstract {
$articlePath = $a->href;
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT);
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT)
or returnServerError('Could not request: ' . $articlePath);
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
$contents = $articlePageHtml->find('div.Contents', 0);
$item['uri'] = $articlePath;
$item['title'] = $contents->find('h3', 0)->innertext;
$item['title'] = $contents->find('h5', 0)->innertext;
$contents->find('h3', 0)->outertext = '';
$contents->find('h5', 0)->outertext = '';
$item['content'] = $contents->innertext;
$item['timestamp'] = $this->extractDate($a->plaintext);

View File

@@ -10,7 +10,13 @@ class AlbionOnlineBridge extends BridgeAbstract {
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'type' => 'list',
'values' => array(
'2' => 2,
'5' => 5,
'10' => 10,
'15' => 15,
),
'title' => 'Maximum number of items to return',
'defaultValue' => 5,
),
@@ -42,7 +48,8 @@ class AlbionOnlineBridge extends BridgeAbstract {
// Example: https://albiononline.com/en/changelog/1/5
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('Unable to get changelog data from "' . $url . '"!');
foreach ($html->find('li') as $data) {
$item = array();
@@ -65,7 +72,8 @@ class AlbionOnlineBridge extends BridgeAbstract {
}
private function getFullChangelog($url) {
$html = getSimpleHTMLDOMCached($url);
$html = getSimpleHTMLDOMCached($url)
or returnServerError('Unable to load changelog post from "' . $url . '"!');
$html = defaultLinkTo($html, self::URI);
return $html->find('div.small-12.columns', 1)->innertext;
}

View File

@@ -77,7 +77,8 @@ class AllocineFRBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request ' . $this->getURI() . ' !');
$category = array_search(
$this->getInput('category'),

View File

@@ -61,7 +61,8 @@ class AmazonBridge extends BridgeAbstract {
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Amazon.');
foreach($html->find('li.s-result-item') as $element) {

View File

@@ -1,7 +1,7 @@
<?php
class AmazonPriceTrackerBridge extends BridgeAbstract {
const MAINTAINER = 'captn3m0, sal0max';
const MAINTAINER = 'captn3m0';
const NAME = 'Amazon Price Tracker';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
@@ -32,7 +32,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'Sweden' => 'se',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),
@@ -40,15 +39,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
),
));
const PRICE_SELECTORS = array(
'#priceblock_ourprice',
'.priceBlockBuyingPriceString',
'#newBuyBoxPrice',
'#tp_price_block_total_price_ww',
'span.offer-price',
'.a-color-price',
);
protected $title;
/**
@@ -63,7 +53,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
*/
public function getURI() {
if (!is_null($this->getInput('asin'))) {
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
}
return parent::getURI();
}
@@ -155,30 +145,14 @@ EOT;
}
private function scrapePriceGeneric($html) {
$priceDiv = null;
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
foreach(self::PRICE_SELECTORS as $sel) {
$priceDiv = $html->find($sel, 0);
if ($priceDiv) {
break;
}
}
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
if (!$priceDiv) {
return false;
}
$priceString = $priceDiv->plaintext;
preg_match('/[\d.,]+/', $priceString, $matches);
$price = $matches[0];
$currency = trim(str_replace($price, '', $priceString), " \t\n\r\0\x0B\xC2\xA0");
if ($price != null && $currency != null) {
if (count($matches) === 3) {
return array(
'price' => $price,
'currency' => $currency,
'price' => $matches[2],
'currency' => $matches[1],
'shipping' => '0'
);
}
@@ -201,8 +175,6 @@ EOT;
'title' => $this->title,
'uri' => $this->getURI(),
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
// This is to ensure that feed readers notice the price change
'uid' => md5($data['price'])
);
if ($data['shipping'] !== '0') {

View File

@@ -142,7 +142,8 @@ class AnidexBridge extends BridgeAbstract {
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url, $headers, $opt);
$html = getSimpleHTMLDOM($search_url, $headers, $opt)
or returnServerError('Could not request Anidex: ' . $search_url);
$links = $html->find('a');
$results = array();
foreach ($links as $link)

View File

@@ -39,7 +39,8 @@ class AnimeUltimeBridge extends BridgeAbstract {
//Retrive page contents
$url = self::URI . 'history-0-1/' . $requestFilter;
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request Anime-Ultime: ' . $url);
//Relases are sorted by day : process each day individually
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
@@ -86,7 +87,8 @@ class AnimeUltimeBridge extends BridgeAbstract {
if(!empty($item_uri)) {
// Retrieve description from description page
$html_item = getContents($item_uri);
$html_item = getContents($item_uri)
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
$item_description = substr(
$html_item,
strpos($html_item, 'class="principal_contain" align="center">') + 41

View File

@@ -35,8 +35,7 @@ class AppleAppStoreBridge extends BridgeAbstract {
'values' => array(
'US' => 'US',
'India' => 'IN',
'Canada' => 'CA',
'Germany' => 'DE',
'Canada' => 'CA'
),
'defaultValue' => 'US',
),

View File

@@ -4,52 +4,59 @@ class AppleMusicBridge extends BridgeAbstract {
const NAME = 'Apple Music';
const URI = 'https://www.apple.com';
const DESCRIPTION = 'Fetches the latest releases from an artist';
const MAINTAINER = 'bockiii';
const MAINTAINER = 'Limero';
const PARAMETERS = array(array(
'artist' => array(
'name' => 'Artist ID',
'exampleValue' => '909253',
'url' => array(
'name' => 'Artist URL',
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
'required' => true,
),
'limit' => array(
'name' => 'Latest X Releases (max 50)',
'defaultValue' => '10',
'imgSize' => array(
'name' => 'Image size for thumbnails (in px)',
'type' => 'number',
'defaultValue' => 512,
'required' => true,
),
)
));
const CACHE_TIMEOUT = 21600; // 6 hours
public function collectData() {
# Limit the amount of releases to 50
if ($this->getInput('limit') > 50) {
$limit = 50;
} else {
$limit = $this->getInput('limit');
}
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$url = 'https://itunes.apple.com/lookup?id='
. $this->getInput('artist')
. '&entity=album&limit='
. $limit .
'&sort=recent';
$html = getSimpleHTMLDOM($url);
$imgSize = $this->getInput('imgSize');
// Grab the json data from the page
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
$html = strstr($html, '{');
$html = substr($html, 0, -9);
$json = json_decode($html);
foreach ($json->results as $obj) {
if ($obj->wrapperType === 'collection') {
// Loop through each object
foreach ($json->included as $obj) {
if ($obj->type === 'lockup/album') {
$this->items[] = array(
'title' => $obj->artistName . ' - ' . $obj->collectionName,
'uri' => $obj->collectionViewUrl,
'timestamp' => $obj->releaseDate,
'enclosures' => $obj->artworkUrl100,
'content' => '<a href=' . $obj->collectionViewUrl
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
. $obj->artistName . ' - ' . $obj->collectionName
. '<br>'
. $obj->copyright,
'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name,
'uri' => $obj->attributes->url,
'timestamp' => $obj->attributes->releaseDate,
'enclosures' => $obj->relationships->artwork->data->id,
);
} elseif ($obj->type === 'image') {
$images[$obj->id] = $obj->attributes->url;
}
}
// Add the images to each item
foreach ($this->items as &$item) {
$item['enclosures'] = array(
str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
);
}
// Sort the order to put the latest albums first
usort($this->items, function($a, $b){
return $a['timestamp'] < $b['timestamp'];
});
}
}

View File

@@ -39,13 +39,15 @@ class ArtStationBridge extends BridgeAbstract {
);
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts);
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts)
or returnServerError('Could not fetch JSON for search query.');
return json_decode($jsonSearchStr);
}
private function fetchProject($hashID) {
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
$jsonProjectStr = getContents($jsonProjectURL);
$jsonProjectStr = getContents($jsonProjectURL)
or returnServerError('Could not fetch JSON for project.');
return json_decode($jsonProjectStr);
}

View File

@@ -1,7 +1,7 @@
<?php
class Arte7Bridge extends BridgeAbstract {
// const MAINTAINER = 'mitsukarenai';
const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min
@@ -91,7 +91,8 @@ class Arte7Bridge extends BridgeAbstract {
'Authorization: Bearer ' . self::API_TOKEN
);
$input = getContents($url, $header);
$input = getContents($url, $header)
or returnServerError('Could not request ARTE.');
$input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) {

View File

@@ -36,7 +36,8 @@ class AsahiShimbunAJWBridge extends BridgeAbstract {
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')));
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')))
or returnServerError('Could not load content');
foreach($html->find('#MainInner li a') as $element) {
if ($element->parent()->class == 'HeadlineTopImage-S') {

View File

@@ -16,7 +16,8 @@ class AskfmBridge extends BridgeAbstract {
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Requested username can\'t be found.');
$html = defaultLinkTo($html, self::URI);

View File

@@ -29,7 +29,8 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract {
public function collectData() {
$uri = self::URI . '/monair/commune/' . $this->getInput('cities');
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request ' . $uri);
$this->dom = $html->find('#block-system-main .city-prevision-map', 0);

View File

@@ -16,7 +16,8 @@ class AtmoOccitanieBridge extends BridgeAbstract {
public function collectData() {
$uri = self::URI . $this->getInput('city');
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request ' . $uri);
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);

View File

@@ -7,14 +7,51 @@ class AutoJMBridge extends BridgeAbstract {
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Afficher les offres de véhicules disponible sur la recheche AutoJM' => array(
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
'url' => array(
'name' => 'URL de la page de recherche',
'name' => 'URL du modèle',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
),
'energy' => array(
'name' => 'Carburant',
'type' => 'list',
'values' => array(
'-' => '',
'Diesel' => 1,
'Essence' => 3,
'Hybride' => 5
),
'title' => 'Carburant'
),
'transmission' => array(
'name' => 'Transmission',
'type' => 'list',
'values' => array(
'-' => '',
'Automatique' => 1,
'Manuelle' => 2
),
'title' => 'Transmission'
),
'priceMin' => array(
'name' => 'Prix minimum',
'type' => 'number',
'required' => false,
'title' => 'Prix minimum du véhicule',
'exampleValue' => '10000',
'defaultValue' => '0'
),
'priceMax' => array(
'name' => 'Prix maximum',
'type' => 'number',
'required' => false,
'title' => 'Prix maximum du véhicule',
'exampleValue' => '15000',
'defaultValue' => '150000'
)
)
);
const CACHE_TIMEOUT = 3600;
@@ -25,8 +62,10 @@ class AutoJMBridge extends BridgeAbstract {
public function getName() {
switch($this->queriedContext) {
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
return 'AutoJM | Recherche de véhicules';
case 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM':
$html = getSimpleHTMLDOMCached(self::URI . $this->getInput('url'), 86400);
$name = html_entity_decode($html->find('title', 0)->plaintext);
return $name;
break;
default:
return parent::getName();
@@ -36,8 +75,13 @@ class AutoJMBridge extends BridgeAbstract {
public function collectData() {
// Get the number of result for this search
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
$model_url = self::URI . $this->getInput('url');
// Build the GET data
$get_data = 'form[energy]=' . $this->getInput('energy') .
'&form[transmission]=' . $this->getInput('transmission') .
'&form[priceMin]=' . $this->getInput('priceMin') .
'&form[priceMin]=' . $this->getInput('priceMin');
// Set the header 'X-Requested-With' like the website does it
$header = array(
@@ -45,91 +89,57 @@ class AutoJMBridge extends BridgeAbstract {
);
// Get the JSON content of the form
$json = getContents($search_url, $header);
$json = getContents($model_url . '?' . $get_data, $header)
or returnServerError('Could not request AutoJM.');
// Extract the HTML content from the JSON result
$data = json_decode($json);
$html = str_get_html($data->results);
$nb_results = $data->nbResults;
$total_pages = ceil($nb_results / 15);
// Go through every car of the model
$list = $html->find('div[class=car-card]');
foreach ($list as $car) {
// Limit the number of page to analyse to 10
for($page = 1; $page <= $total_pages && $page <= 10; $page++) {
// Get the result the next page
$html = $this->getResults($page);
// Go through every car of the search
$list = $html->find('div[class*=card-car card-car--listing]');
foreach ($list as $car) {
// Get the info about the car offer
$image = $car->find('div[class=card-car__header__img]', 0)->find('img', 0)->src;
// Decode HTML attribute JSON data
$car_data = json_decode(html_entity_decode($car->{'data-layer'}));
$car_model = $car->{'data-title'} . ' ' . $car->{'data-suptitle'};
$availability = $car->find('div[class=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
$warranty = $car->find('div[data-type=WarrantyCard]', 0)->plaintext;
$discount_html = $car->find('div[class=subtext vehicle_reference_element]', 0);
// Check if there is any discount info displayed
if ($discount_html != null) {
$reference_price_value = $discount_html->find('span[data-cfg=vehicle__reference_price]', 0)->plaintext;
$discount_percent_value = $discount_html->find('span[data-cfg=vehicle__discount_percent]', 0)->plaintext;
$reference_price = '<li>Prix de référence : <s>' . $reference_price_value . '</s></li>';
$discount_percent = '<li>Réduction : ' . $discount_percent_value . ' %</li>';
} else {
$reference_price = '';
$discount_percent = '';
}
$price = $car_data->price;
$kilometer = $car->find('span[data-cfg=vehicle__kilometer]', 0)->plaintext;
$energy = $car->find('span[data-cfg=vehicle__energy__label]', 0)->plaintext;
$power = $car->find('span[data-cfg=vehicle__tax_horse_power]', 0)->plaintext;
$seats = $car->find('span[data-cfg=vehicle__seats]', 0)->plaintext;
$doors = $car->find('span[data-cfg=vehicle__door__label]', 0)->plaintext;
$transmission = $car->find('span[data-cfg=vehicle__transmission]', 0)->plaintext;
$loa_html = $car->find('span[data-cfg=vehicle__loa]', 0);
// Check if any LOA price is displayed
if($loa_html != null) {
$loa_value = $car->find('span[data-cfg=vehicle__loa]', 0)->plaintext;
$loa = '<li>LOA : à partir de ' . $loa_value . ' / mois </li>';
} else {
$loa = '';
}
// Construct the new item
$item = array();
$item['title'] = $car_model;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
. $car_model . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
$item['content'] .= '<li>Prix : ' . $price . ' €</li>';
$item['content'] .= $reference_price;
$item['content'] .= $loa;
$item['content'] .= $discount_percent;
$item['content'] .= '<li>Garantie : ' . $warranty . '</li>';
$item['content'] .= '<li>Kilométrage : ' . $kilometer . ' km</li>';
$item['content'] .= '<li>Energie : ' . $energy . '</li>';
$item['content'] .= '<li>Puissance: ' . $power . ' CV Fiscaux</li>';
$item['content'] .= '<li>Nombre de Places : ' . $seats . ' place(s)</li>';
$item['content'] .= '<li>Nombre de portes : ' . $doors . '</li>';
$item['content'] .= '<li>Boite de vitesse : ' . $transmission . '</li></ul>';
$item['uri'] = $car_data->{'uri'};
$item['uid'] = hash('md5', $item['content']);
$this->items[] = $item;
// Get the Finish name if this car is the first of a new finish
$prev_tag = $car->prev_sibling();
if($prev_tag->tag == 'div' && $prev_tag->class == 'results-title') {
$finish_name = $prev_tag->plaintext;
}
// Get the info about the car offer
$image = $car->find('div[class=car-card__visual]', 0)->find('img', 0)->src;
$serie = $car->find('div[class=car-card__title]', 0)->plaintext;
$url = $car->find('a', 0)->href;
// Check if the car model is in stock or available only on order
if($car->find('span[class*=tag--dispo]', 0) != null) {
$availability = 'En Stock';
} else {
$availability = 'Sur commande';
}
$discount_html = $car->find('span[class=promo]', 0);
// Check if there is any discount dsiplayed
if ($discount_html != null) {
$discount = $discount_html->plaintext;
} else {
$discount = 'inconnue';
}
$price = $car->find('span[class=price]', 0)->plaintext;
// Construct the new item
$item = array();
$item['title'] = $finish_name . ' ' . $serie;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
. $finish_name . ' ' . $serie . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
$item['content'] .= '<li>Série : ' . $serie . '</li>';
$item['content'] .= '<li>Remise : ' . $discount . '</li>';
$item['content'] .= '<li>Prix : ' . $price . '</li></ul>';
// Add a fictionnal anchor to the RSS element URL, based on the item content ;
// As the URL could be identical even if the price change, some RSS reader will not show those offers as new items
$item['uri'] = $url . '#' . md5($item['content']);
$this->items[] = $item;
}
}
private function getResults(int $page)
{
$user_input = $this->getInput('url');
$search_data = preg_replace('#(recherche|recherche/[0-9]{1,10})\?#', 'recherche/' . $page . '?', $user_input);
$search_url = self::URI . $search_data . '&open=energy&onlyFilters=false';
// Get the HTML content of the page
$html = getSimpleHTMLDOMCached($search_url);
return $html;
}
}

View File

@@ -18,7 +18,8 @@ class AwwwardsBridge extends BridgeAbstract {
private function fetchSites() {
Debug::log('Fetching all sites');
$sites = getSimpleHTMLDOM(self::SITESURI);
$sites = getSimpleHTMLDOM(self::SITESURI)
or returnServerError('Could not fetch JSON for sites.');
Debug::log('Parsing all JSON data');
foreach($sites->find('li[data-model]') as $site) {

View File

@@ -220,7 +220,8 @@ class BadDragonBridge extends BridgeAbstract {
public function collectData() {
switch($this->queriedContext) {
case 'Sales':
$sales = json_decode(getContents(self::URI . 'api/sales'));
$sales = json_decode(getContents(self::URI . 'api/sales'))
or returnServerError('Failed to query BD API');
foreach($sales as $sale) {
$item = array();
@@ -273,10 +274,12 @@ class BadDragonBridge extends BridgeAbstract {
}
break;
case 'Clearance':
$toyData = json_decode(getContents($this->inputToURL(true)));
$toyData = json_decode(getContents($this->inputToURL(true)))
or returnServerError('Failed to query BD API');
$productList = json_decode(getContents(self::URI
. 'api/inventory-toy/product-list'));
. 'api/inventory-toy/product-list'))
or returnServerError('Failed to query BD API');
foreach($toyData->toys as $toy) {
$item = array();

View File

@@ -3,80 +3,23 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
const NAME = 'Baka Updates Manga Releases';
const URI = 'https://www.mangaupdates.com/';
const DESCRIPTION = 'Get the latest series releases';
const MAINTAINER = 'fulmeek, KamaleiZestri';
const PARAMETERS = array(
'By series' => array(
'series_id' => array(
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '12345'
)
),
'By list' => array(
'list_id' => array(
'name' => 'List ID and Type',
'type' => 'text',
'required' => true,
'exampleValue' => '123456&list=read'
)
const MAINTAINER = 'fulmeek';
const PARAMETERS = array(array(
'series_id' => array(
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '12345'
)
);
));
const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
const RELEASES_URL = 'https://www.mangaupdates.com/releases.html';
private $feedName = '';
public function collectData() {
if($this -> queriedContext == 'By series')
$this -> collectDataBySeries();
else //queriedContext == 'By list'
$this -> collectDataByList();
}
public function getURI(){
if($this -> queriedContext == 'By series') {
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
} else //queriedContext == 'By list'
return self::RELEASES_URL;
return self::URI;
}
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
private function getSanitizedHash($string) {
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
}
private function filterText($text) {
return rtrim($text, '* ');
}
private function filterHTML($text) {
return $this->filterText(html_entity_decode($text));
}
private function findID($manga) {
// sometimes new series are on the release list that have no ID. just drop them.
if(@$this -> filterHTML($manga -> find('a', 0) -> href) != null) {
preg_match('/id=([0-9]*)/', $this -> filterHTML($manga -> find('a', 0) -> href), $match);
return $match[1];
} else
return 0;
}
private function collectDataBySeries() {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Series not found');
// content is an unstructured pile of divs, ugly to parse
$cols = $html->find('div#main_content div.row > div.text');
@@ -125,62 +68,36 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
$item['title'] = implode(' ', $title);
$item['uri'] = $this->getURI();
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$item['uid'] = $this->getSanitizedHash($item['title']);
$this->items[] = $item;
}
}
private function collectDataByList() {
$this -> feedName = 'Releases';
$list = array();
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
$list_id = $this -> getInput('list_id');
$listHTML = getSimpleHTMLDOM('https://www.mangaupdates.com/mylist.html?id=' . $list_id);
//get ids of the manga that the user follows,
$parts = $listHTML -> find('table#ptable tr > td.pl');
foreach($parts as $part) {
$list[] = $this -> findID($part);
public function getURI(){
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
return self::URI;
}
//similar to above, but the divs are in groups of 3.
$cols = $releasesHTML -> find('div#main_content div.row > div.pbreak');
$rows = array_slice(array_chunk($cols, 3), 0);
foreach($rows as $cols) {
//check if current manga is in user's list.
$id = $this -> findId($cols[0]);
if(!array_search($id, $list)) continue;
$item = array();
$title = array();
$item['content'] = '';
$objTitle = $cols[0];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterHTML($objTitle -> innertext) . '</p>';
}
$objVolChap = $cols[1];
if ($objVolChap && !empty($objVolChap->plaintext))
$title[] = $this -> filterHTML($objVolChap -> innertext);
$objAuthor = $cols[2];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor -> plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterHTML($objAuthor -> innertext) . '</p>';
}
$item['title'] = implode(' ', $title);
$item['uri'] = self::URI . 'releases.html?search=' . $id . '&stype=series';
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$this->items[] = $item;
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
private function getSanitizedHash($string) {
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
}
private function filterText($text) {
return rtrim($text, '* ');
}
private function filterHTML($text) {
return $this->filterText(html_entity_decode($text));
}
}

View File

@@ -38,30 +38,6 @@ class BandcampBridge extends BridgeAbstract {
'defaultValue' => 5
)
),
'By label' => array(
'label' => array(
'name' => 'label',
'type' => 'text',
'title' => 'label name as seen in the label page URL',
'required' => true
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'changes'
),
'limit' => array(
'name' => 'limit',
'type' => 'number',
'title' => 'Number of releases to return',
'defaultValue' => 5
)
),
'By album' => array(
'band' => array(
'name' => 'band',
@@ -110,7 +86,8 @@ class BandcampBridge extends BridgeAbstract {
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
);
$content = getContents($url, $header, $opts);
$content = getContents($url, $header, $opts)
or returnServerError('Could not complete request to: ' . $url);
$json = json_decode($content);
@@ -145,15 +122,12 @@ class BandcampBridge extends BridgeAbstract {
}
break;
case 'By band':
case 'By label':
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$titleElement = $html->find('head meta[name=title]', 0)
or returnServerError('Unable to find title on: ' . $this->getURI());
$this->feedName = $titleElement->content;
$regex = '/band_id=(\d+)/';
if(preg_match($regex, $html, $matches) == false)
@@ -163,7 +137,6 @@ class BandcampBridge extends BridgeAbstract {
$tralbums = array();
switch($this->queriedContext) {
case 'By band':
case 'By label':
$query_data = array(
'band_id' => $band_id
);
@@ -299,7 +272,8 @@ class BandcampBridge extends BridgeAbstract {
private function apiGet($endpoint, $query_data) {
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
$data = json_decode(getContents($url));
$data = json_decode(getContents($url))
or returnServerError('API request to "' . $url . '" failed.');
return $data;
}
@@ -313,13 +287,6 @@ class BandcampBridge extends BridgeAbstract {
. '?sort_field=date';
}
break;
case 'By label':
if(!is_null($this->getInput('label'))) {
return 'https://'
. $this->getInput('label')
. '.bandcamp.com/music';
}
break;
case 'By band':
if(!is_null($this->getInput('band'))) {
return 'https://'
@@ -354,13 +321,6 @@ class BandcampBridge extends BridgeAbstract {
return $this->getInput('band') . ' - Bandcamp Band';
}
break;
case 'By label':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Label';
} elseif(!is_null($this->getInput('label'))) {
return $this->getInput('label') . ' - Bandcamp Label';
}
break;
case 'By album':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Album';

View File

@@ -8,7 +8,8 @@ class BastaBridge extends BridgeAbstract {
const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend')
or returnServerError('Could not request Bastamag.');
$limit = 0;
@@ -18,11 +19,13 @@ class BastaBridge extends BridgeAbstract {
$item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$item['content'] = $html->find('div.texte', 0)->innertext;
// Replaces all relative image URLs by absolute URLs.
// Relative URLs always start with 'local/'!
$item['content'] = preg_replace(
'/src=["\']{1}([^"\']+)/ims',
'src=\'' . self::URI . '$1\'',
getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext
);
$this->items[] = $item;
$limit++;
}

View File

@@ -35,23 +35,28 @@ class BinanceBridge extends BridgeAbstract {
}
protected function collectBlogData() {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not fetch Binance blog data.');
$appData = $html->find('script[id="__APP_DATA"]');
$appDataJson = json_decode($appData[0]->innertext);
foreach($html->find('div[direction="row"]') as $element) {
foreach($appDataJson->pageData->redux->blogList->blogList as $element) {
$date = $element->find('div[direction="column"]', 0);
$day = $date->find('div', 0)->innertext;
$month = $date->find('div', 1)->innertext;
$extractedDate = $day . ' ' . $month;
$date = $element->postTime;
$abstract = $element->brief;
$uri = self::URI . '/' . $element->lang . '/blog/' . $element->idStr;
$title = $element->title;
$content = $element->content;
$abstract = $element->find('div[direction="column"]', 1);
$a = $abstract->find('a', 0);
$uri = self::URI . $a->href;
$title = $a->innertext;
$full = getSimpleHTMLDOMCached($uri);
$content = $full->find('div.desc', 1);
$item = array();
$item['title'] = $title;
$item['uri'] = $uri;
$item['timestamp'] = substr($date, 0, -3);
$item['timestamp'] = strtotime($extractedDate);
$item['author'] = 'Binance';
$item['content'] = $content;
@@ -63,7 +68,8 @@ class BinanceBridge extends BridgeAbstract {
}
protected function collectAnnouncementData() {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not fetch Zendesk announcement data.');
foreach($html->find('a.article-list-link') as $a) {
$title = $a->innertext;

View File

@@ -0,0 +1,119 @@
<?php
class BingSearchBridge extends BridgeAbstract
{
const NAME = 'Bing search';
const URI = 'https://www.bing.com/';
const DESCRIPTION = 'Return images from bing search discover';
const MAINTAINER = 'DnAp';
const PARAMETERS = array(
'Image Discover' => array(
'category' => array(
'name' => 'Categories',
'type' => 'list',
'values' => self::IMAGE_DISCOVER_CATEGORIES
),
'image_size' => array(
'name' => 'Image size',
'type' => 'list',
'values' => array(
'Small' => 'turl',
'Full size' => 'imgurl'
)
)
)
);
const IMAGE_DISCOVER_CATEGORIES = array(
'Abstract' => 'abstract',
'Animals' => 'animals',
'Anime' => 'anime',
'Architecture' => 'architecture',
'Arts and Crafts' => 'arts-and-crafts',
'Beauty' => 'beauty',
'Cars and Motorcycles' => 'cars-and-motorcycles',
'Cats' => 'cats',
'Celebrities' => 'celebrities',
'Comics' => 'comics',
'DIY' => 'diy',
'Dogs' => 'dogs',
'Fitness' => 'fitness',
'Food and Drink' => 'food-and-drink',
'Funny' => 'funny',
'Gadgets' => 'gadgets',
'Gardening' => 'gardening',
'Geeky' => 'geeky',
'Hairstyles' => 'hairstyles',
'Home Decor' => 'home-decor',
'Marine Life' => 'marine-life',
'Men\'s Fashion' => 'men%27s-fashion',
'Nature' => 'nature',
'Outdoors' => 'outdoors',
'Parenting' => 'parenting',
'Phone Wallpapers' => 'phone-wallpapers',
'Photography' => 'photography',
'Quotes' => 'quotes',
'Recipes' => 'recipes',
'Snow' => 'snow',
'Tattoos' => 'tattoos',
'Travel' => 'travel',
'Video Games' => 'video-games',
'Weddings' => 'weddings',
'Women\'s Fashion' => 'women%27s-fashion',
);
public function getIcon()
{
return 'https://www.bing.com/sa/simg/bing_p_rr_teal_min.ico';
}
public function collectData()
{
$this->items = $this->imageDiscover($this->getInput('category'));
}
public function getName()
{
if ($this->getInput('category')) {
if (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) {
$category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')];
} else {
$category = 'Unknown';
}
return 'Best ' . $category . ' - Bing Image Discover';
}
return parent::getName();
}
private function imageDiscover($category)
{
$html = getSimpleHTMLDOM(self::URI . '/discover/' . $category)
or returnServerError('Could not request ' . self::NAME);
$sizeKey = $this->getInput('image_size');
$items = array();
foreach ($html->find('a.iusc') as $element) {
$data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true);
$item = array();
$item['title'] = basename(rtrim($data['imgurl'], '/'));
$item['uri'] = $data['imgurl'];
$item['content'] = '<a href="' . $data['imgurl'] . '">
<img src="' . $data[$sizeKey] . '" alt="' . $item['title'] . '"></a>
<p>Source: <a href="' . $this->curUrl($data['surl']) . '"> </a></p>';
$item['enclosures'] = $data['imgurl'];
$items[] = $item;
}
return $items;
}
private function curUrl($url)
{
if (strlen($url) <= 80) {
return $url;
}
return substr($url, 0, 80) . '...';
}
}

View File

@@ -13,7 +13,8 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request BDM.');
foreach($html->find('div.blague') as $element) {

View File

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

View File

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

View File

@@ -16,7 +16,6 @@ class BrutBridge extends BridgeAbstract {
'Entertainment' => 'entertainment',
'Sports' => 'sport',
'Nature' => 'nature',
'Health' => 'health',
),
'defaultValue' => 'news',
),
@@ -27,7 +26,6 @@ class BrutBridge extends BridgeAbstract {
'United States' => 'us',
'United Kingdom' => 'uk',
'France' => 'fr',
'Spain' => 'es',
'India' => 'in',
'Mexico' => 'mx',
),
@@ -44,7 +42,8 @@ class BrutBridge extends BridgeAbstract {
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$results = $html->find('div.results', 0);
@@ -53,7 +52,8 @@ class BrutBridge extends BridgeAbstract {
$videoPath = self::URI . $li->children(0)->href;
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600);
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600)
or returnServerError('Could not request: ' . $videoPath);
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;

View File

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

View File

@@ -41,7 +41,8 @@ class BundesbankBridge extends BridgeAbstract {
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No response for ' . $this->getURI());
$html = defaultLinkTo($html, $this->getURI());

View File

@@ -53,7 +53,8 @@ class CNETBridge extends BridgeAbstract {
// Retrieve webpage
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
$html = getSimpleHTMLDOM($pageUrl);
$html = getSimpleHTMLDOM($pageUrl)
or returnServerError('Could not request CNET: ' . $pageUrl);
// Process articles
foreach($html->find('div.assetBody, div.riverPost') as $element) {

View File

@@ -1,140 +0,0 @@
<?php
// CVE Details is a collection of CVEs, taken from the National Vulnerability
// Database (NVD) and other sources like the Exploit DB and Metasploit. The
// website categorizes it by vendor and product and attach the CWE category.
// There is a Atom feed available, but only logged in users can use it,
// it is not reliable and contain no useful information. This bridge create a
// sane feed with additional information like tags and a link to the CWE
// a description of the vulnerability.
class CVEDetailsBridge extends BridgeAbstract {
const MAINTAINER = 'Aaron Fischer';
const NAME = 'CVE Details';
const CACHE_TIMEOUT = 60 * 60 * 6; // 6 hours
const DESCRIPTION = 'Report new CVE vulnerabilities for a given vendor (and product)';
const URI = 'https://www.cvedetails.com';
const PARAMETERS = array(array(
// The Vendor ID can be taken from the URL
'vendor_id' => array(
'name' => 'Vendor ID',
'type' => 'number',
'required' => true,
'exampleValue' => 74, // PHP
),
// The optional Product ID can be taken from the URL as well
'product_id' => array(
'name' => 'Product ID',
'type' => 'number',
'required' => false,
'exampleValue' => 128, // PHP
),
));
private $html = null;
private $vendor = '';
private $product = '';
// Return the URL to query.
// Because of the optional product ID, we need to attach it if it is
// set. The search result page has the exact same structure (with and
// without the product ID).
private function _buildURL() {
$url = self::URI . '/vulnerability-list/vendor_id-' . $this->getInput('vendor_id');
if ($this->getInput('product_id') !== '') {
$url .= '/product_id-' . $this->getInput('product_id');
}
// Sadly, there is no way (prove me wrong please) to sort the search
// result by publish date. So the nearest alternative is the CVE
// number, which should be mostly accurate.
$url .= '?order=1'; // Order by CVE number DESC
return $url;
}
// Make the actual request to cvedetails.com and stores the response
// (HTML) for later use and extract vendor and product from it.
private function _fetchContent() {
$html = getSimpleHTMLDOM($this->_buildURL());
$this->html = defaultLinkTo($html, self::URI);
$vendor = $html->find('#contentdiv > h1 > a', 0);
if ($vendor == null) {
returnServerError('Invalid Vendor ID ' .
$this->getInput('vendor_id') .
' or Product ID ' .
$this->getInput('product_id'));
}
$this->vendor = $vendor->innertext;
$product = $html->find('#contentdiv > h1 > a', 1);
if ($product != null) {
$this->product = $product->innertext;
}
}
// Build the name of the feed.
public function getName() {
if ($this->getInput('vendor_id') == '') {
return self::NAME;
}
if ($this->html == null) {
$this->_fetchContent();
}
$name = 'CVE Vulnerabilities for ' . $this->vendor;
if ($this->product != '') {
$name .= '/' . $this->product;
}
return $name;
}
// Pull the data from the HTML response and fill the items..
public function collectData() {
if ($this->html == null) {
$this->_fetchContent();
}
foreach ($this->html->find('#vulnslisttable .srrowns') as $i => $tr) {
// There are some optional vulnerability types, which will be
// added to the categories as well as the CWE number -- which is
// always given.
$categories = array($this->vendor);
$enclosures = array();
$cwe = $tr->find('td', 2)->find('a', 0);
if ($cwe != null) {
$cwe = $cwe->innertext;
$categories[] = 'CWE-' . $cwe;
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe . '.html';
}
$c = $tr->find('td', 4)->innertext;
if (trim($c) != '') {
$categories[] = $c;
}
if ($this->product != '') {
$categories[] = $this->product;
}
// The CVE number itself
$title = $tr->find('td', 1)->find('a', 0)->innertext;
$this->items[] = array(
'uri' => $tr->find('td', 1)->find('a', 0)->href,
'title' => $title,
'timestamp' => $tr->find('td', 5)->innertext,
'content' => $tr->next_sibling()->innertext,
'categories' => $categories,
'enclosures' => $enclosures,
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
);
// We only want to fetch the latest 10 CVEs
if (count($this->items) >= 10) {
break;
}
}
}
}

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

@@ -13,7 +13,7 @@ class CeskaTelevizeBridge extends BridgeAbstract {
'url' => array(
'name' => 'url to the show',
'required' => true,
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/'
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/dily/'
)
)
);
@@ -38,7 +38,7 @@ class CeskaTelevizeBridge extends BridgeAbstract {
public function collectData() {
$url = $this->getInput('url');
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(bonus\/)?$/';
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(dily\/((nove|vysilani)\/)?)?$/';
if (!preg_match($validUrl, $url, $match)) {
returnServerError('Invalid url');
}
@@ -46,7 +46,8 @@ class CeskaTelevizeBridge extends BridgeAbstract {
$category = isset($match[4]) ? $match[4] : 'nove';
$fixedUrl = "{$match[1]}dily/{$category}/";
$html = getSimpleHTMLDOM($fixedUrl);
$html = getSimpleHTMLDOM($fixedUrl)
or returnServerError('Could not request Česká televize');
$this->feedUri = $fixedUrl;
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
@@ -54,17 +55,17 @@ class CeskaTelevizeBridge extends BridgeAbstract {
$this->feedName .= " ({$category})";
}
foreach ($html->find('#episodeListSection a[data-testid=next-link]') as $element) {
$itemTitle = $element->find('h3', 0);
$itemContent = $element->find('div[class^=content-]', 0);
$itemDate = $element->find('div[class^=playTime-] span', 0);
foreach ($html->find('.episodes-broadcast-content a.episode_list_item') as $element) {
$itemTitle = $element->find('.episode_list_item-title', 0);
$itemContent = $element->find('.episode_list_item-desc', 0);
$itemDate = $element->find('.episode_list_item-date', 0);
$itemThumbnail = $element->find('img', 0);
$itemUri = self::URI . $element->getAttribute('href');
$item = array(
'title' => $this->fixChars($itemTitle->plaintext),
'uri' => $itemUri,
'content' => '<img src="' . $itemThumbnail->getAttribute('src') . '" /><br />'
'content' => '<img src="https:' . $itemThumbnail->getAttribute('src') . '" /><br />'
. $this->fixChars($itemContent->plaintext),
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
);
@@ -73,8 +74,8 @@ class CeskaTelevizeBridge extends BridgeAbstract {
}
}
public function getURI() {
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
public function getUri() {
return isset($this->feedUri) ? $this->feedUri : parent::getUri();
}
public function getName() {

View File

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

View File

@@ -1,387 +0,0 @@
<?php
class CodebergBridge extends BridgeAbstract {
const NAME = 'Codeberg Bridge';
const URI = 'https://codeberg.org/';
const DESCRIPTION = 'Returns commits, issues, pull requests or releases for a repository.';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'Commits' => array(
'branch' => array(
'name' => 'branch',
'type' => 'text',
'exampleValue' => 'main',
'required' => false,
'title' => 'Optional, main branch is used by default.',
),
),
'Issues' => array(),
'Issue Comments' => array(
'issueId' => array(
'name' => 'Issue ID',
'type' => 'text',
'required' => true,
)
),
'Pull Requests' => array(),
'Releases' => array(),
'global' => array(
'username' => array(
'name' => 'Username',
'type' => 'text',
'exampleValue' => 'username',
'title' => 'Username of account that the repository belongs to.',
'required' => true,
),
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'exampleValue' => 'repo',
'required' => true,
)
)
);
const CACHE_TIMEOUT = 1800;
const TEST_DETECT_PARAMETERS = array(
'https://codeberg.org/Codeberg/Community/issues/507' => array(
'context' => 'Issue Comments', 'username' => 'Codeberg', 'repo' => 'Community', 'issueId' => '507'
),
'https://codeberg.org/Codeberg/Community/issues' => array(
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/pulls' => array(
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/releases' => array(
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/commits/branch/master' => array(
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
),
'https://codeberg.org/Codeberg/Community/commits' => array(
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
)
);
private $defaultBranch = 'main';
private $issueTitle = '';
private $urlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)(?:\/commits\/branch\/([\w]+))?/';
private $issuesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues/';
private $pullsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/pulls/';
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
public function detectParameters($url) {
$params = array();
// Issue Comments
if(preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
$params['context'] = 'Issue Comments';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
$params['issueId'] = $matches[3];
return $params;
}
// Issues
if(preg_match($this->issuesUrlRegex, $url, $matches)) {
$params['context'] = 'Issues';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Pull Requests
if(preg_match($this->pullsUrlRegex, $url, $matches)) {
$params['context'] = 'Pull Requests';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Releases
if(preg_match($this->releasesUrlRegex, $url, $matches)) {
$params['context'] = 'Releases';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Commits
if(preg_match($this->urlRegex, $url, $matches)) {
$params['context'] = 'Commits';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
if (isset($matches[3])) {
$params['branch'] = $matches[3];
}
return $params;
}
return null;
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI());
$html = defaultLinkTo($html, $this->getURI());
switch($this->queriedContext) {
case 'Commits':
$this->extractCommits($html);
break;
case 'Issues':
$this->extractIssues($html);
break;
case 'Issue Comments':
$this->extractIssueComments($html);
break;
case 'Pull Requests':
$this->extractPulls($html);
break;
case 'Releases':
$this->extractReleases($html);
break;
default:
returnClientError('Invalid context: ' . $this->queriedContext);
}
}
public function getName() {
switch($this->queriedContext) {
case 'Commits':
if ($this->getBranch() === $this->defaultBranch) {
return $this->getRepo() . ' Commits';
}
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
case 'Issues':
return $this->getRepo() . ' Issues - ' . self::NAME;
case 'Issue Comments':
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
case 'Pull Requests':
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
case 'Releases':
return $this->getRepo() . ' Releases - ' . self::NAME;
default:
return parent::getName();
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Commits':
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
case 'Issues':
return self::URI . $this->getRepo() . '/issues/';
case 'Issue Comments':
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
case 'Pull Requests':
return self::URI . $this->getRepo() . '/pulls';
case 'Releases':
return self::URI . $this->getRepo() . '/releases';
default:
return parent::getURI();
}
}
private function getBranch() {
if ($this->getInput('branch')) {
return $this->getInput('branch');
}
return $this->defaultBranch;
}
private function getRepo() {
return $this->getInput('username') . '/' . $this->getInput('repo');
}
private function extractCommits($html) {
$table = $html->find('table#commits-table', 0);
$tbody = $table->find('tbody.commit-list', 0);
foreach ($tbody->find('tr') as $tr) {
$item = array();
$message = $tr->find('td.message', 0);
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
$item['author'] = $tr->find('td.author', 0)->plaintext;
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
if ($message->find('pre.commit-body', 0)) {
$message->find('pre.commit-body', 0)->style = '';
$item['content'] = $message->find('pre.commit-body', 0);
} else {
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
}
$this->items[] = $item;
}
}
private function extractIssues($html) {
$div = $html->find('div.repository', 0);
foreach ($div->find('li.item') as $li) {
$item = array();
$number = $li->find('div', 0)->plaintext;
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
$item['uri'] = $li->find('a.title', 0)->href;
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
// Fetch issue page
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
$issuePage = defaultLinkTo($issuePage, self::URI);
$item['content'] = $issuePage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
$this->items[] = $item;
}
}
private function extractIssueComments($html) {
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
. ' (' . $html->find('span.index', 0)->plaintext . ')';
foreach ($html->find('ui.timeline > div.timeline-item.comment') as $div) {
$item = array();
if ($div->class === 'timeline-item comment merge box') {
continue;
}
$item['title'] = $this->ellipsisTitle($div->find('div.render-content.markdown', 0)->plaintext);
$item['uri'] = $div->find('span.text.grey', 0)->find('a', 1)->href;
$item['content'] = $div->find('div.render-content.markdown', 0);
if ($div->find('div.dropzone-attachments', 0)) {
$item['content'] .= $div->find('div.dropzone-attachments', 0);
}
$item['author'] = $div->find('a.author', 0)->innertext;
$item['timestamp'] = $div->find('span.time-since', 0)->title;
$this->items[] = $item;
}
}
private function extractPulls($html) {
$div = $html->find('div.repository', 0);
foreach ($div->find('li.item') as $li) {
$item = array();
$number = $li->find('div', 0)->plaintext;
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
$item['uri'] = $li->find('a.title', 0)->href;
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
// Fetch pull request page
$pullRequestPage = getSimpleHTMLDOMCached($item['uri'], 3600);
$pullRequestPage = defaultLinkTo($pullRequestPage, self::URI);
$item['content'] = $pullRequestPage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
$this->items[] = $item;
}
}
private function extractReleases($html) {
$ul = $html->find('ul#release-list', 0);
foreach ($ul->find('li.ui.grid') as $li) {
$item = array();
if ($li->find('h3', 0)) { // Release
$item['title'] = $li->find('h3', 0)->plaintext;
$item['uri'] = $li->find('h3', 0)->find('a', 0)->href;
$tag = $li->find('span.tag', 0)->find('a', 0);
$commit = $li->find('span.commit', 0);
$downloads = $this->extractDownloads($li->find('div.download', 0));
$item['content'] = $li->find('div.markdown', 0);
$item['content'] .= <<<HTML
<strong>Tag</strong>
<p>{$tag}</p>
<strong>Commit</strong>
<p>{$commit}</p>
{$downloads}
HTML;
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
}
if ($li->find('h4', 0)) { // Tag
$item['title'] = $li->find('h4', 0)->plaintext;
$item['uri'] = $li->find('h4', 0)->find('a', 0)->href;
$item['content'] = <<<HTML
<strong>Commit</strong>
<p>{$li->find('div.download', 0)->find('a', 0)}</p>
HTML;
$item['content'] .= $this->extractDownloads($li->find('div.download', 0), true);
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
}
$this->items[] = $item;
}
}
private function extractDownloads($html, $skipFirst = false) {
$downloads = '';
foreach ($html->find('a') as $index => $a) {
if ($skipFirst === true && $index === 0) {
continue;
}
$downloads .= <<<HTML
{$a}<br>
HTML;
}
return <<<EOD
<strong>Downloads</strong>
<p>{$downloads}</p>
EOD;
}
private function ellipsisTitle($text) {
$length = 100;
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
return $text;
}
}

View File

@@ -34,7 +34,8 @@ class CollegeDeFranceBridge extends BridgeAbstract {
* </li>
*/
$html = getSimpleHTMLDOM(self::URI
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all')
or returnServerError('Could not request CollegeDeFrance.');
foreach($html->find('a[data-target]') as $element) {
$item = array();

View File

@@ -7,11 +7,8 @@ class ComboiosDePortugalBridge extends BridgeAbstract {
const MAINTAINER = 'somini';
public function collectData() {
# Do not verify SSL certificate (the server doesn't send the intermediate)
# https://github.com/RSS-Bridge/rss-bridge/issues/2397
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', array(), array(
CURLOPT_SSL_VERIFYPEER => 0,
));
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos')
or returnServerError('Could not load content');
foreach($html->find('.warnings-table a') as $element) {
$item = array();

View File

@@ -15,17 +15,20 @@ class ComicsKingdomBridge extends BridgeAbstract {
));
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false);
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false)
or returnServerError('Could not request Comics Kingdom: ' . $this->getURI());
// Get author from first page
$author = $html->find('div.author p', 0);;
$author = $html->find('div.author p', 0)->plaintext
or returnServerError('Comics Kingdom comic does not exist: ' . $this->getURI());;
// Get current date/link
$link = $html->find('meta[property=og:url]', 0)->content;
for($i = 0; $i < 5; $i++) {
$item = array();
$page = getSimpleHTMLDOM($link);
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request Comics Kingdom: ' . $link);
$imagelink = $page->find('meta[property=og:image]', 0)->content;
$prevSlug = $page->find('slider-arrow[:is-left-arrow=true]', 0);

View File

@@ -26,7 +26,8 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
);
private function getReleaseFeed($jsonUrl) {
$json = getContents($jsonUrl);
$json = getContents($jsonUrl)
or returnServerError('Could not request Core OS Website.');
return json_decode($json, true);
}

View File

@@ -8,7 +8,8 @@ class CopieDoubleBridge extends BridgeAbstract {
const DESCRIPTION = 'CopieDouble';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request CopieDouble.');
$table = $html->find('table table', 2);

View File

@@ -1,27 +1,55 @@
<?php
class CourrierInternationalBridge extends FeedExpander {
class CourrierInternationalBridge extends BridgeAbstract {
const MAINTAINER = 'teromene';
const NAME = 'Courrier International Bridge';
const URI = 'https://www.courrierinternational.com/';
const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = 'Returns the newest articles';
const DESCRIPTION = 'Courrier International bridge';
public function collectData(){
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
}
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Error.');
protected function parseItem($feedItem){
$item = parent::parseItem($feedItem);
$element = $html->find('article');
$article_count = 1;
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
$content = $articlePage->find('.article-text', 0);
if(!$content) {
$content = $articlePage->find('.depeche-text', 0);
foreach($element as $article) {
$item = array();
$item['uri'] = $article->parent->getAttribute('href');
if(strpos($item['uri'], 'http') === false) {
$item['uri'] = self::URI . $item['uri'];
}
$page = getSimpleHTMLDOMCached($item['uri']);
$content = $page->find('.article-text', 0);
if(!$content) {
$content = $page->find('.depeche-text', 0);
}
$item['content'] = sanitize($content);
$item['title'] = strip_tags($article->find('.title', 0));
$dateTime = date_parse($page->find('time', 0));
$item['timestamp'] = mktime(
$dateTime['hour'],
$dateTime['minute'],
$dateTime['second'],
$dateTime['month'],
$dateTime['day'],
$dateTime['year']
);
$this->items[] = $item;
$article_count ++;
if($article_count > 5)
break;
}
$item['content'] = sanitize($content);
return $item;
}
}

View File

@@ -17,7 +17,8 @@ class CryptomeBridge extends BridgeAbstract {
));
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Cryptome.');
$number = $this->getInput('n');

View File

@@ -19,7 +19,8 @@ class CuriousCatBridge extends BridgeAbstract {
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
$apiJson = getContents($url);
$apiJson = getContents($url)
or returnServerError('Could not request: ' . $url);
$apiData = json_decode($apiJson, true);

View File

@@ -46,7 +46,8 @@ class DailymotionBridge extends BridgeAbstract {
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
$apiJson = getContents($this->getApiUrl());
$apiJson = getContents($this->getApiUrl())
or returnServerError('Could not request: ' . $this->getApiUrl());
$apiData = json_decode($apiJson, true);
@@ -71,7 +72,8 @@ class DailymotionBridge extends BridgeAbstract {
if ($this->queriedContext === 'From search results') {
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Dailymotion.');
foreach($html->find('div.media a.preview_link') as $element) {
$item = array();
@@ -178,7 +180,8 @@ class DailymotionBridge extends BridgeAbstract {
$url = self::URI . 'playlist/' . $id;
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
return $title;

View File

@@ -57,7 +57,8 @@ class DanbooruBridge extends BridgeAbstract {
}
public function collectData(){
$content = getContents($this->getFullURI());
$content = getContents($this->getFullURI())
or returnServerError('Could not request ' . $this->getName());
$html = Fix_Simple_Html_Dom::str_get_html($content);

View File

@@ -9,7 +9,8 @@ class DansTonChatBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'latest.html');
$html = getSimpleHTMLDOM(self::URI . 'latest.html')
or returnServerError('Could not request DansTonChat.');
foreach($html->find('div.item') as $element) {
$item = array();

View File

@@ -48,24 +48,22 @@ class DarkReadingBridge extends FeedExpander {
if ($feed_id != '000') {
$feed_url .= '?f_n=' . $feed_id . '&f_ln=' . $feed_name;
}
$this->collectExpandableDatas($feed_url, 20);
$this->collectExpandableDatas($feed_url);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$article = getSimpleHTMLDOMCached($item['uri']);
if (empty($item['content']))
return null; //ignore dummy articles
$article = getSimpleHTMLDOMCached($item['uri'])
or returnServerError('Could not request Dark Reading: ' . $item['uri']);
$item['content'] = $this->extractArticleContent($article);
$item['enclosures'] = array(); //remove author profile picture
$image = $article->find('meta[property="og:image"]', 0);
if (is_object($image)) {
$image = $image->content;
$item['enclosures'] = array($image);
}
return $item;
}
private function extractArticleContent($article){
$content = $article->find('div.article-content', 0)->innertext;
$content = $article->find('div#article-main', 0)->innertext;
foreach (array(
'<div class="divsplitter',
@@ -76,6 +74,8 @@ class DarkReadingBridge extends FeedExpander {
$content = stripRecursiveHTMLSection($content, 'div', $div_start);
}
$content = stripWithDelimiters($content, '<h1 ', '</h1>');
return $content;
}
}

View File

@@ -9,7 +9,8 @@ class DaveRamseyBlogBridge extends BridgeAbstract {
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request daveramsey.com.');
foreach ($html->find('.Post') as $element) {
$this->items[] = array(

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
class DerpibooruBridge extends BridgeAbstract {
const NAME = 'Derpibooru Bridge';
const URI = 'https://derpibooru.org/';
const DESCRIPTION = 'Returns newest images from a Derpibooru search';
const DESCRIPTION = 'Returns newest posts from a Derpibooru search';
const CACHE_TIMEOUT = 300; // 5min
const MAINTAINER = 'Roliga';
@@ -73,42 +73,40 @@ class DerpibooruBridge extends BridgeAbstract {
public function collectData(){
$queryJson = json_decode(getContents(
self::URI
. 'api/v1/json/search/images?filter_id='
. 'search.json?filter_id='
. urlencode($this->getInput('f'))
. '&q='
. urlencode($this->getInput('q'))
));
)) or returnServerError('Failed to query Derpibooru');
foreach($queryJson->images as $post) {
foreach($queryJson->search as $post) {
$item = array();
$postUri = self::URI . $post->id;
$item['uri'] = $postUri;
$item['title'] = $post->name;
$item['title'] = $post->id;
$item['timestamp'] = strtotime($post->created_at);
$item['author'] = $post->uploader;
$item['enclosures'] = array($post->view_url);
$item['categories'] = $post->tags;
$item['enclosures'] = array('https:' . $post->image);
$item['categories'] = explode(', ', $post->tags);
$item['content'] = '<p><a href="' // image preview
. $postUri
. '"><img src="'
. '"><img src="https:'
. $post->representations->medium
. '"></a></p><p>' // description
. $post->description
. '</p><p><b>Size:</b> ' // image size
. $post->width
. 'x'
. $post->height;
// source link
if ($post->source_url != null) {
$item['content'] .= '<br><b>Source:</b> <a href="'
. $post->height
. '<br><b>Source:</b> <a href="' // source link
. $post->source_url
. '">'
. $post->source_url
. '</a></p>';
};
$this->items[] = $item;
}
}

View File

@@ -155,7 +155,8 @@ class DesoutterBridge extends BridgeAbstract {
}
*/
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, $this->getURI());
@@ -183,7 +184,8 @@ class DesoutterBridge extends BridgeAbstract {
}
private function getFullNewsArticle($uri) {
$html = getSimpleHTMLDOMCached($uri);
$html = getSimpleHTMLDOMCached($uri)
or returnServerError('Unable to load full article!');
$html = defaultLinkTo($html, $this->getURI());
@@ -197,7 +199,8 @@ class DesoutterBridge extends BridgeAbstract {
* @return void
*/
private function extractNewsLanguages() {
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events');
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events')
or returnServerError('Error loading news!');
$html = defaultLinkTo($html, static::URI);
@@ -222,7 +225,8 @@ class DesoutterBridge extends BridgeAbstract {
* @return void
*/
private function extractIndustryLanguages() {
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news');
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news')
or returnServerError('Error loading news!');
$html = defaultLinkTo($html, static::URI);

View File

@@ -45,7 +45,8 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
}
public function collectData() {
$html = getSimpleHTMLDOMCached($this->getURI());
$html = getSimpleHTMLDOMCached($this->getURI())
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, static::URI);
@@ -94,7 +95,8 @@ EOD;
}
private function getFullArticle($url) {
$html = getSimpleHTMLDOMCached($url);
$html = getSimpleHTMLDOMCached($url)
or returnServerError('Unable to load article from "' . $url . '"!');
$html = defaultLinkTo($html, static::URI);

View File

@@ -42,7 +42,6 @@ class DiarioDeNoticiasBridge extends BridgeAbstract {
}
return $name;
}
public function getURI() {
switch($this->queriedContext) {
case 'Tag':
@@ -56,7 +55,8 @@ class DiarioDeNoticiasBridge extends BridgeAbstract {
public function collectData() {
$archives = self::getURI();
$html = getSimpleHTMLDOMCached($archives);
$html = getSimpleHTMLDOMCached($archives)
or returnServerError('Could not load content');
foreach($html->find('article') as $element) {
$item = array();

View File

@@ -27,7 +27,8 @@ class DiarioDoAlentejoBridge extends BridgeAbstract {
public function collectData(){
/* This is slow as molasses (>30s!), keep the cache timeout high to avoid killing the host */
$html = getSimpleHTMLDOMCached($this->getURI() . '/pt/noticias-listagem.aspx');
$html = getSimpleHTMLDOMCached($this->getURI() . '/pt/noticias-listagem.aspx')
or returnServerError('Could not load content');
foreach($html->find('.list_news .item') as $element) {
$item = array();

View File

@@ -97,7 +97,8 @@ class DiceBridge extends BridgeAbstract {
$uri .= '&telecommute=true';
}
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Dice.');
foreach($html->find('div.complete-serp-result-div') as $element) {
$item = array();
// Title

View File

@@ -9,7 +9,8 @@ class DilbertBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Dilbert: ' . self::URI);
foreach($html->find('section.comic-item') as $element) {

View File

@@ -44,11 +44,13 @@ class DiscogsBridge extends BridgeAbstract {
if(!empty($this->getInput('artistid'))) {
$data = getContents('https://api.discogs.com/artists/'
. $this->getInput('artistid')
. '/releases?sort=year&sort_order=desc');
. '/releases?sort=year&sort_order=desc')
or returnServerError('Unable to query discogs !');
} elseif(!empty($this->getInput('labelid'))) {
$data = getContents('https://api.discogs.com/labels/'
. $this->getInput('labelid')
. '/releases?sort=year&sort_order=desc');
. '/releases?sort=year&sort_order=desc')
or returnServerError('Unable to query discogs !');
}
$jsonData = json_decode($data, true);
@@ -74,7 +76,8 @@ class DiscogsBridge extends BridgeAbstract {
if(!empty($this->getInput('username_wantlist'))) {
$data = getContents('https://api.discogs.com/users/'
. $this->getInput('username_wantlist')
. '/wants?sort=added&sort_order=desc');
. '/wants?sort=added&sort_order=desc')
or returnServerError('Unable to query discogs !');
$jsonData = json_decode($data, true)['wants'];
} elseif(!empty($this->getInput('username_folder'))) {
@@ -82,7 +85,8 @@ class DiscogsBridge extends BridgeAbstract {
. $this->getInput('username_folder')
. '/collection/folders/'
. $this->getInput('folderid')
. '/releases?sort=added&sort_order=desc');
. '/releases?sort=added&sort_order=desc')
or returnServerError('Unable to query discogs !');
$jsonData = json_decode($data, true)['releases'];
}
foreach($jsonData as $element) {

View File

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

View File

@@ -40,7 +40,8 @@ class DonnonsBridge extends BridgeAbstract {
private function collectDataByPage($page) {
$uri = $this->getPageURI($page);
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('No results for this query.');
$searchDiv = $html->find('div[id=search]', 0);

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,8 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Error while downloading the website content');
$json = $this->loadEmbeddedJsonData($html);
@@ -85,7 +86,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
private function getImageTag($preview_path, $title){
return sprintf(
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>',
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
$this->getFullSizeImagePath($preview_path),
$preview_path,
$title
@@ -93,11 +94,6 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
}
private function getFullSizeImagePath($preview_path){
// Get last image from srcset
$src_set_urls = explode(',', $preview_path);
$url = end($src_set_urls);
$url = explode(' ', $url)[1];
return htmlspecialchars_decode($url);
return explode("?compress=1", $preview_path)[0];
}
}

View File

@@ -1,205 +0,0 @@
<?php
class Drive2ruBridge extends BridgeAbstract {
const MAINTAINER = 'dotter-ak';
const NAME = 'Drive2.ru';
const URI = 'https://drive2.ru/';
const DESCRIPTION = 'Лента новостей и тестдрайвов, бортжурналов по выбранной марке или модели
(также работает с фильтром по категориям), блогов пользователей и публикаций по темам.';
const PARAMETERS = array(
'Новости и тест-драйвы' => array(),
'Бортжурналы (По модели или марке)' => array(
'url' => array(
'name' => 'Ссылка на страницу с бортжурналом',
'type' => 'text',
'required' => true,
'title' => 'Например: https://www.drive2.ru/experience/suzuki/g4895/',
'exampleValue' => 'https://www.drive2.ru/experience/suzuki/g4895/'
),
),
'Личные блоги' => array(
'username' => array(
'name' => 'Никнейм пользователя на сайте',
'type' => 'text',
'required' => true,
'title' => 'Например: Mickey',
'exampleValue' => 'Mickey'
)
),
'Публикации по темам (Стоит почитать)' => array(
'topic' => array(
'name' => 'Темы',
'type' => 'list',
'values' => array(
'Автозвук' => '16',
'Автомобильный дизайн' => '10',
'Автоспорт' => '11',
'Автошоу, музеи, выставки' => '12',
'Безопасность' => '18',
'Беспилотные автомобили' => '15',
'Видеосюжеты' => '20',
'Вне дорог' => '21',
'Встречи' => '22',
'Выбор и покупка машины' => '23',
'Гаджеты' => '30',
'Гибридные машины' => '32',
'Грузовики, автобусы, спецтехника' => '31',
'Доработка интерьера' => '35',
'Законодательство' => '40',
'История автомобилестроения' => '50',
'Мототехника' => '60',
'Новые модели и концепты' => '85',
'Обучение вождению' => '70',
'Путешествия' => '80',
'Ремонт и обслуживание' => '90',
'Реставрация ретро-авто' => '91',
'Сделай сам' => '104',
'Смешное' => '103',
'Спорткары' => '102',
'Стайлинг' => '101',
'Тест-драйвы' => '110',
'Тюнинг' => '111',
'Фотосессии' => '120',
'Шины и диски' => '140',
'Электрика' => '130',
'Электромобили' => '131'
),
'defaultValue' => '16',
)
),
'global' => array(
'full_articles' => array(
'name' => 'Загружать в ленту полный текст',
'type' => 'checkbox'
)
)
);
private $title;
private function getUserContent($url) {
$html = getSimpleHTMLDOM($url);
$this->title = $html->find('title', 0)->innertext;
$articles = $html->find('div.js-entity');
foreach ($articles as $article) {
$item = array();
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
if($this->getInput('full_articles')) {
$item['content'] = $this->addCommentsLink(
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
$item['uri']
);
} else {
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
}
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
$this->items[] = $item;
}
}
private function getLogbooksContent($url) {
$html = getSimpleHTMLDOM($url);
$this->title = $html->find('title', 0)->innertext;
$articles = $html->find('div.js-entity');
foreach ($articles as $article) {
$item = array();
$item['title'] = $article->find('a.c-link--text', 1)->plaintext;
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 1)->href);
if($this->getInput('full_articles')) {
$item['content'] = $this->addCommentsLink(
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
$item['uri']
);
} else {
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
}
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
$this->items[] = $item;
}
}
private function getNews() {
$html = getSimpleHTMLDOM('https://www.drive2.ru/editorial/');
$this->title = $html->find('title', 0)->innertext;
$articles = $html->find('div.c-article-card');
foreach ($articles as $article) {
$item = array();
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
if($this->getInput('full_articles')) {
$item['content'] = $this->addCommentsLink(
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.article', 0))->innertext,
$item['uri']
);
} else {
$item['content'] = $this->addReadMoreLink($article->find('div.c-article-card__lead', 0), $item['uri']);
}
$item['author'] = 'Новости и тест-драйвы на Drive2.ru';
if (!is_null($article->find('img', 0))) $item['enclosures'][] = $article->find('img', 0)->src;
$this->items[] = $item;
}
}
private function adjustContent($content) {
foreach ($content->find('div.o-group') as $node)
$node->outertext = '';
foreach($content->find('div, span') as $attrs)
foreach ($attrs->getAllAttributes() as $attr => $val)
$attrs->removeAttribute($attr);
foreach ($content->getElementsByTagName('figcaption') as $attrs)
$attrs->setAttribute(
'style',
'font-style: italic; font-size: small; margin: 0 100px 75px;');
foreach ($content->find('script') as $node)
$node->outertext = '';
foreach ($content->find('iframe') as $node) {
preg_match('/embed\/(.*?)\?/', $node->src, $match);
$node->outertext = '<a href="https://www.youtube.com/watch?v=' . $match[1] .
'">https://www.youtube.com/watch?v=' . $match[1] . '</a>';
}
return $content;
}
private function addCommentsLink ($content, $url) {
return $content . '<br><a href="' . $url . '#comments">Перейти к комментариям</a>';
}
private function addReadMoreLink ($content, $url) {
if (!is_null($content))
return preg_replace('!\s+!', ' ', str_replace('Читать дальше', '', $content->plaintext)) .
'<br><a href="' . $url . '">Читать далее</a>';
else return '';
}
public function collectData() {
switch($this->queriedContext) {
default:
case 'Новости и тест-драйвы':
$this->getNews();
break;
case 'Бортжурналы (По модели или марке)':
if (!preg_match('/^https:\/\/www.drive2.ru\/experience/', $this->getInput('url')))
returnServerError('Invalid url');
$this->getLogbooksContent($this->getInput('url'));
break;
case 'Личные блоги':
if (!preg_match('/^[a-zA-Z0-9-]{3,16}$/', $this->getInput('username')))
returnServerError('Invalid username');
$this->getUserContent('https://www.drive2.ru/users/' . $this->getInput('username'));
break;
case 'Публикации по темам (Стоит почитать)':
$this->getUserContent('https://www.drive2.ru/topics/' . $this->getInput('topic'));
break;
}
}
public function getName() {
return $this->title ?: parent::getName();
}
public function getIcon() {
return 'https://www.drive2.ru/favicon.ico';
}
}

View File

@@ -28,13 +28,14 @@ class DuckDuckGoBridge extends BridgeAbstract {
));
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'));
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'))
or returnServerError('Could not request DuckDuckGo.');
foreach($html->find('div.result') as $element) {
foreach($html->find('div.results_links') as $element) {
$item = array();
$item['uri'] = $element->find('a.result__a', 0)->href;
$item['title'] = $element->find('h2.result__title', 0)->plaintext;
$item['content'] = $element->find('a.result__snippet', 0)->plaintext;
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = $element->find('a', 1)->innertext;
$item['content'] = $element->find('div.snippet', 0)->plaintext;
$this->items[] = $item;
}
}

View File

@@ -107,7 +107,8 @@ class ETTVBridge extends BridgeAbstract {
// Get results page
$this->results_link = self::URI . $query_str;
$html = getSimpleHTMLDOM($this->results_link);
$html = getSimpleHTMLDOM($this->results_link)
or returnServerError('Could not request ' . $this->getName());
// Loop on each entry
foreach($html->find('table.table tr') as $element) {
@@ -116,7 +117,8 @@ class ETTVBridge extends BridgeAbstract {
// retrieve result page to get more details
$link = rtrim(self::URI, '/') . $entry->href;
$page = getSimpleHTMLDOM($link);
$page = getSimpleHTMLDOM($link)
or returnServerError('Could not request page ' . $link);
// get details & download links
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…

View File

@@ -35,7 +35,8 @@ on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
foreach($showList as $showID) {
// Get show page
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/');
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/')
or returnServerError('Could not request EZTV for id "' . $showID . '"');
// Loop on each element that look like an episode entry...
foreach($html->find('.forum_header_border') as $element) {

View File

@@ -1,128 +1,63 @@
<?php
class EconomistBridge extends FeedExpander {
class EconomistBridge extends BridgeAbstract {
const NAME = 'The Economist: Latest Updates';
const URI = 'https://www.economist.com';
const DESCRIPTION = 'Fetches the latest updates from the Economist.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
const MAINTAINER = 'bockiii';
const NAME = 'Economist Bridge';
const URI = 'https://www.economist.com/';
const CACHE_TIMEOUT = 3600; //1hour
const DESCRIPTION = 'Returns the latest articles for the selected category';
const PARAMETERS = array(
'global' => array(
'limit' => array(
'name' => 'Feed Item Limit',
'required' => true,
'type' => 'number',
'defaultValue' => 10,
'title' => 'Maximum number of returned feed items. Maximum 30, default 10'
)
),
'Topics' => array(
'topic' => array(
'name' => 'Topics',
'type' => 'list',
'title' => 'Select a Topic',
'defaultValue' => 'latest',
'values' => array(
'Latest' => 'latest',
'The world this week' => 'the-world-this-week',
'Letters' => 'letters',
'Leaders' => 'leaders',
'Briefings' => 'briefing',
'Special reports' => 'special-report',
'Britain' => 'britain',
'Europe' => 'europe',
'United States' => 'united-states',
'The Americas' => 'the-americas',
'Middle East and Africa' => 'middle-east-and-africa',
'Asia' => 'asia',
'China' => 'china',
'International' => 'international',
'Business' => 'business',
'Finance and economics' => 'finance-and-economics',
'Science and technology' => 'science-and-technology',
'Books and arts' => 'books-and-arts',
'Obituaries' => 'obituary',
'Graphic detail' => 'graphic-detail',
'Indicators' => 'economic-and-financial-indicators',
)
)
),
'Blogs' => array(
'blog' => array(
'name' => 'Blogs',
'type' => 'list',
'title' => 'Select a Blog',
'values' => array(
'Bagehots notebook' => 'bagehots-notebook',
'Bartleby' => 'bartleby',
'Buttonwoods notebook' => 'buttonwoods-notebook',
'Charlemagnes notebook' => 'charlemagnes-notebook',
'Democracy in America' => 'democracy-in-america',
'Erasmus' => 'erasmus',
'Free exchange' => 'free-exchange',
'Game theory' => 'game-theory',
'Gulliver' => 'gulliver',
'Kaffeeklatsch' => 'kaffeeklatsch',
'Prospero' => 'prospero',
'The Economist Explains' => 'the-economist-explains',
)
)
)
);
public function collectData(){
// get if topics or blogs were selected and store the selected category
switch ($this->queriedContext) {
case 'Topics':
$category = $this->getInput('topic');
break;
case 'Blogs':
$category = $this->getInput('blog');
break;
default:
$category = 'latest';
}
// limit the returned articles to 30 at max
if ((int)$this->getInput('limit') <= 30) {
$limit = (int)$this->getInput('limit');
} else {
$limit = 30;
}
$this->collectExpandableDatas('https://www.economist.com/' . $category . '/rss.xml', $limit);
public function getIcon() {
return 'https://www.economist.com/sites/default/files/econfinal_favicon.ico';
}
protected function parseItem($feedItem){
$item = parent::parseItem($feedItem);
public function collectData() {
$html = getSimpleHTMLDOM(self::URI . '/latest/')
or returnServerError('Could not fetch latest updates form The Economist.');
$article = getSimpleHTMLDOM($item['uri']);
// before the article can be added, it needs to be cleaned up, thus, the extra function
$item['content'] = $this->cleanContent($article);
// only the article lead image is retained if it's there
if (!is_null($article->find('div.article__lead-image', 0))) {
$item['enclosures'][] = $article->find('div.article__lead-image', 0)->find('img', 0)->getAttribute('src');
} else {
$item['enclosures'][] = '';
foreach($html->find('article') as $element) {
$a = $element->find('a', 0);
$href = self::URI . $a->href;
$full = getSimpleHTMLDOMCached($href);
$article = $full->find('article', 0);
$header = $article->find('h1', 0);
$author = $article->find('span[itemprop="author"]', 0);
$time = $article->find('time[itemprop="dateCreated"]', 0);
$content = $article->find('div[itemprop="description"]', 0);
// Remove newsletter subscription box
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
if ($newsletter)
$newsletter->outertext = '';
$newsletterForm = $content->find('form', 0);
if ($newsletterForm)
$newsletterForm->outertext = '';
// Remove next and previous article URLs at the bottom
$nextprev = $content->find('div[class="blog-post__next-previous-wrapper"]', 0);
if ($nextprev)
$nextprev->outertext = '';
$section = array( $article->find('h3[itemprop="articleSection"]', 0)->plaintext );
$item = array();
$item['title'] = $header->find('span', 0)->innertext . ': '
. $header->find('span', 1)->innertext;
$item['uri'] = $href;
$item['timestamp'] = strtotime($time->datetime);
$item['author'] = $author->innertext;
$item['categories'] = $section;
$item['content'] = '<img style="max-width: 100%" src="'
. $a->find('img', 0)->src . '">' . $content->innertext;
$this->items[] = $item;
if (count($this->items) >= 10)
break;
}
return $item;
}
private function cleanContent($article){
// the actual article is in this div
$content = $article->find('div.layout-article-body', 0)->innertext;
// clean the article content. Remove all div's since the text is in paragraph elements
foreach (array(
'<div '
) as $tag_start) {
$content = stripRecursiveHTMLSection($content, 'div', $tag_start);
}
// now remove embedded iframes. The podcast postings contain these for example
$content = preg_replace('/<iframe.*?\/iframe>/i', '', $content);
// fix the relative links
$content = defaultLinkTo($content, $this->getURI());
return $content;
}
}

View File

@@ -25,7 +25,8 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
$language = $this->getInput('language');
$url = 'https://community.elitedangerous.com/';
$url = $url . $language . '/galnet';
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('Error while downloading the website content');
foreach($html->find('div.article') as $element) {
$item = array();

View File

@@ -63,7 +63,8 @@ class ElsevierBridge extends BridgeAbstract {
public function collectData(){
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('No results for Elsevier journal ' . $this->getInput('j'));
foreach($html->find('.pod-listing') as $article) {
$item = array();

View File

@@ -10,7 +10,14 @@ class EpicgamesBridge extends BridgeAbstract {
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'type' => 'list',
'values' => array(
'5' => 5,
'10' => 10,
'15' => 15,
'20' => 20,
'25' => 25,
),
'title' => 'Maximum number of items to return',
'defaultValue' => 10,
),
@@ -50,8 +57,10 @@ class EpicgamesBridge extends BridgeAbstract {
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog?limit=25
$urlBlog = $api . $this->getInput('language') . '/content/blog?limit=' . $this->getInput('postcount');
$dataSticky = getContents($urlSticky);
$dataBlog = getContents($urlBlog);
$dataSticky = getContents($urlSticky)
or returnServerError('Unable to get the sticky posts from epicgames.com!');
$dataBlog = getContents($urlBlog)
or returnServerError('Unable to get the news posts from epicgames.com!');
// Merge data
$decodedData = array_merge(json_decode($dataSticky), json_decode($dataBlog));

View File

@@ -42,7 +42,8 @@ class EsquerdaNetBridge extends FeedExpander {
$item = parent::parseItem($newsItem);
# Include all the content
$uri = $item['uri'];
$html = getSimpleHTMLDOMCached($uri);
$html = getSimpleHTMLDOMCached($uri)
or returnServerError('Could not load content for ' . $uri);
$content = $html->find('div#content div.content', 0);
## Fix author
$authorHTML = $html->find('.field-name-field-op-author a', 0);

View File

@@ -8,7 +8,8 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
const DESCRIPTION = 'Should we put a website in production today? (French)';
public function collectData() {
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
$item = array();
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');

View File

@@ -33,7 +33,8 @@ class EtsyBridge extends BridgeAbstract {
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed to receive ' . $this->getURI());
$results = $html->find('li.block-grid-item');

View File

@@ -1,59 +0,0 @@
<?php
class ExplosmBridge extends BridgeAbstract {
const MAINTAINER = 'bockiii';
const NAME = 'Explosm Bridge';
const URI = 'https://www.explosm.net/';
const CACHE_TIMEOUT = 4800; //2hours
const DESCRIPTION = 'Returns the last 5 comics';
const PARAMETERS = array(
'Get latest posts' => array(
'limit' => array(
'name' => 'Posts limit',
'type' => 'number',
'title' => 'Maximum number of items to return',
'defaultValue' => 5
)
)
);
public function collectData(){
$limit = $this->getInput('limit');
$latest = getSimpleHTMLDOM('https://explosm.net/comics/latest');
$image = $latest->find('div[id=comic]', 0)->find('img', 0)->getAttribute('src');
$date_string = $latest->find('p[class*=Author__P]', 0)->innertext;
$next_data_string = $latest->find('script[id=__NEXT_DATA__]', 0)->innertext;
$exp = '/{\\\"latest\\\":\[{\\\"slug\\\":\\\"(.*?)\\ /';
$reg_array = array();
preg_match($exp, $next_data_string, $reg_array);
$comic_id = $reg_array[1];
$comic_id = substr($comic_id, 0, strpos($comic_id, '\\'));
$item = array();
$item['uri'] = $this::URI . 'comics/' . $comic_id;
$item['uid'] = $this::URI . 'comics/' . $comic_id;
$item['title'] = 'Comic for ' . $date_string;
$item['timestamp'] = strtotime($date_string);
$item['author'] = $latest->find('p[class*=Author__P]', 2)->innertext;
$item['content'] = '<img src="' . $image . '" />';
$this->items[] = $item;
$next_comic = substr($this::URI, 0, -1)
. $latest->find('div[class*=MainComic__Selector]', 0)->find('a', 0)->getAttribute('href');
// use index 1 as the latest comic was already found
for ($i = 1; $i <= $limit; $i++) {
$this_comic = getSimpleHTMLDOM($next_comic);
$image = $this_comic->find('div[id=comic]', 0)->find('img', 0)->getAttribute('src');
$date_string = $this_comic->find('p[class*=Author__P]', 0)->innertext;
$item = array();
$item['uri'] = $next_comic;
$item['uid'] = $next_comic;
$item['title'] = 'Comic for ' . $date_string;
$item['timestamp'] = strtotime($date_string);
$item['author'] = $this_comic->find('p[class*=Author__P]', 2)->innertext;
$item['content'] = '<img src="' . $image . '" />';
$this->items[] = $item;
$next_comic = substr($this::URI, 0, -1)
. $this_comic->find('div[class*=MainComic__Selector]', 0)->find('a', 0)->getAttribute('href'); // get next comic link
}
}
}

View File

@@ -1,7 +1,7 @@
<?php
class ExtremeDownloadBridge extends BridgeAbstract {
const NAME = 'Extreme Download';
const URI = 'https://www.extreme-down.plus/';
const URI = 'https://www.extreme-down.ninja/';
const DESCRIPTION = 'Suivi de série sur Extreme Download';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
@@ -10,7 +10,7 @@ class ExtremeDownloadBridge extends BridgeAbstract {
'name' => 'URL de la série',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une série sans le https://www.extreme-down.plus/',
'title' => 'URL d\'une série sans le https://ww1.extreme-d0wn.com/',
'exampleValue' => 'series-hd/hd-series-vostfr/46631-halt-and-catch-fire-saison-04-vostfr-hdtv-720p.html'),
'filter' => array(
'name' => 'Type de contenu',
@@ -26,7 +26,8 @@ class ExtremeDownloadBridge extends BridgeAbstract {
);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'));
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
or returnServerError('Could not request Extreme Download.');
$filter = $this->getInput('filter');
@@ -80,16 +81,6 @@ class ExtremeDownloadBridge extends BridgeAbstract {
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkType($element)
{
$return = '';

View File

@@ -24,7 +24,8 @@ class FDroidBridge extends BridgeAbstract {
public function collectData(){
$url = self::URI;
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request F-Droid.');
// targetting the corresponding widget based on user selection
// "updated" is the 5th widget on the page, "added" is the 6th

View File

@@ -28,15 +28,16 @@ class FM4Bridge extends BridgeAbstract
)
);
private function getPageData($tag, $page) {
public function getPageData($tag, $page) {
if($tag)
$uri = self::URI . '/tags/' . $tag;
$uri = self::URI . "/tags/" . $tag;
else
$uri = self::URI;
$uri = $uri . '?page=' . $page;
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri)
or returnServerError('Error while downloading the website content');
$page_items = array();
@@ -46,20 +47,20 @@ class FM4Bridge extends BridgeAbstract
$item['uri'] = $article->find('a', 0)->href;
$item['title'] = $article->find('h2', 0)->plaintext;
$item['author'] = $article->find('p[class*=keyword]', 0)->plaintext;
$item['timestamp'] = strtotime($article->find('p[class*=time]', 0)->plaintext);
$item["timestamp"] = strtotime($article->find('p[class*=time]', 0)->plaintext);
if ($this->getInput('loadcontent')) {
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0);
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
or returnServerError('Error while downloading the full article');
}
$page_items[] = $item;
}
return $page_items;
}
public function collectData() {
for ($cur_page = 1; $cur_page <= $this->getInput('pages'); $cur_page++) {
$this->items = array_merge($this->items, $this->getPageData($this->getInput('tag'), $cur_page));
$this->items = array_merge($this->items,$this->getPageData($this->getInput('tag'), $cur_page));
}
}
}

View File

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

View File

@@ -6,7 +6,8 @@ class FabriceBellardBridge extends BridgeAbstract {
const MAINTAINER = 'somini';
public function collectData() {
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not load content');
foreach ($html->find('p') as $obj) {
$item = array();

View File

@@ -1,10 +1,10 @@
<?php
class FacebookBridge extends BridgeAbstract {
// const MAINTAINER = 'teromene, logmanoriginal';
const MAINTAINER = 'teromene, logmanoriginal';
const NAME = 'Facebook Bridge | Main Site';
const URI = 'https://www.facebook.com/';
const CACHE_TIMEOUT = 1800; // 30min
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
please insert the parameter as follow : myExamplePage/132621766841117';
@@ -175,13 +175,8 @@ class FacebookBridge extends BridgeAbstract {
$header = array();
}
$touchURI = str_replace(
'https://www.facebook',
'https://touch.facebook',
$this->getURI()
);
$html = getSimpleHTMLDOM($touchURI, $header);
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('Failed loading facebook page: ' . $this->getURI());
if(!$this->isPublicGroup($html)) {
returnClientError('This group is not public! RSS-Bridge only supports public groups!');
@@ -191,18 +186,19 @@ class FacebookBridge extends BridgeAbstract {
$this->groupName = $this->extractGroupName($html);
$posts = $html->find('div.story_body_container')
$posts = $html->find('div.userContentWrapper')
or returnServerError('Failed finding posts!');
foreach($posts as $post) {
$item = array();
$item['uri'] = $this->extractGroupPostURI($post);
$item['title'] = $this->extractGroupPostTitle($post);
$item['author'] = $this->extractGroupPostAuthor($post);
$item['content'] = $this->extractGroupPostContent($post);
$item['enclosures'] = $this->extractGroupPostEnclosures($post);
$item['uri'] = $this->extractGroupURI($post);
$item['title'] = $this->extractGroupTitle($post);
$item['author'] = $this->extractGroupAuthor($post);
$item['content'] = $this->extractGroupContent($post);
$item['timestamp'] = $this->extractGroupTimestamp($post);
$item['enclosures'] = $this->extractGroupEnclosures($post);
$this->items[] = $item;
@@ -219,7 +215,16 @@ class FacebookBridge extends BridgeAbstract {
$urlparts = parse_url($group);
$this->validateHost($urlparts['host']);
if($urlparts['host'] !== parse_url(self::URI)['host']
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
return explode('/', $urlparts['path'])[2];
@@ -231,47 +236,24 @@ class FacebookBridge extends BridgeAbstract {
}
private function validateHost($provided_host) {
// Handle mobile links
if (strpos($provided_host, 'm.') === 0) {
$provided_host = substr($provided_host, strlen('m.'));
}
if (strpos($provided_host, 'touch.') === 0) {
$provided_host = substr($provided_host, strlen('touch.'));
}
$facebook_host = parse_url(self::URI)['host'];
if ($provided_host !== $facebook_host
&& 'www.' . $provided_host !== $facebook_host) {
returnClientError('The host you provided is invalid! Received "'
. $provided_host
. '", expected "'
. $facebook_host
. '"!');
}
}
/**
* @param $html simple_html_dom
* @return bool
*/
private function isPublicGroup($html) {
// Facebook touch just presents a login page for non-public groups
$title = $html->find('title', 0);
return $title->plaintext !== 'Log in to Facebook | Facebook';
// Facebook redirects to the groups about page for non-public groups
$about = $html->find('#pagelet_group_about', 0);
return !($about);
}
private function extractGroupName($html) {
$ogtitle = $html->find('._de1', 0)
$ogtitle = $html->find('meta[property="og:title"]', 0)
or returnServerError('Unable to find group title!');
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES);
return html_entity_decode($ogtitle->content, ENT_QUOTES);
}
private function extractGroupPostURI($post) {
private function extractGroupURI($post) {
$elements = $post->find('a')
or returnServerError('Unable to find URI!');
@@ -280,8 +262,7 @@ class FacebookBridge extends BridgeAbstract {
// Find the one that is a permalink
if(strpos($anchor->href, 'permalink') !== false) {
$arr = explode('?', $anchor->href, 2);
return $arr[0];
return $anchor->href;
}
}
@@ -290,61 +271,57 @@ class FacebookBridge extends BridgeAbstract {
}
private function extractGroupPostContent($post) {
private function extractGroupContent($post) {
$content = $post->find('div._5rgt', 0)
$content = $post->find('div.userContent', 0)
or returnServerError('Unable to find user content!');
$context_text = $content->innertext;
if ($content->next_sibling() !== null) {
$context_text .= $content->next_sibling()->innertext;
}
return $context_text;
return $content->innertext . $content->next_sibling()->innertext;
}
private function extractGroupPostAuthor($post) {
private function extractGroupTimestamp($post) {
$element = $post->find('h3 a', 0)
$element = $post->find('abbr[data-utime]', 0)
or returnServerError('Unable to find timestamp!');
return $element->getAttribute('data-utime');
}
private function extractGroupAuthor($post) {
$element = $post->find('img', 0)
or returnServerError('Unable to find author information!');
return $element->plaintext;
return $element->{'aria-label'};
}
private function extractGroupPostEnclosures($post) {
private function extractGroupEnclosures($post) {
$elements = $post->find('span._6qdm');
if ($post->find('div._5rgt', 0)->next_sibling() !== null) {
array_push($elements, ...$post->find('div._5rgt', 0)->next_sibling()->find('i.img'));
}
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
$enclosures = array();
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
foreach($elements as $enclosure) {
if(preg_match($background_img_regex, $enclosure, $matches) > 0) {
$bg_img_value = trim(html_entity_decode($matches[1], ENT_QUOTES), "'\"");
$bg_img_url = urldecode(preg_replace('/\\\([0-9a-z]{2}) /', '%$1', $bg_img_value));
$enclosures[] = urldecode($bg_img_url);
}
$enclosures[] = $enclosure->src;
}
return empty($enclosures) ? null : $enclosures;
}
private function extractGroupPostTitle($post) {
private function extractGroupTitle($post) {
$element = $post->find('h3', 0)
$element = $post->find('h5', 0)
or returnServerError('Unable to find title!');
if(strpos($element->plaintext, 'shared') === false) {
$content = strip_tags($this->extractGroupPostContent($post));
$content = strip_tags($this->extractGroupContent($post));
return $this->extractGroupPostAuthor($post)
return $this->extractGroupAuthor($post)
. ' posted: '
. substr(
$content,
@@ -371,7 +348,13 @@ class FacebookBridge extends BridgeAbstract {
$urlparts = parse_url($user);
$this->validateHost($urlparts['host']);
if($urlparts['host'] !== parse_url(self::URI)['host']) {
returnClientError('The host you provided is invalid! Received "'
. $urlparts['host']
. '", expected "'
. parse_url(self::URI)['host']
. '"!');
}
if(!array_key_exists('path', $urlparts)
|| $urlparts['path'] === '/') {
@@ -533,7 +516,8 @@ EOD;
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
);
$html = getSimpleHTMLDOM($captcha_action, $header, $opts);
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
or returnServerError('Failed to submit captcha response back to Facebook');
return $html;
}
@@ -558,7 +542,8 @@ EOD;
$header = array();
}
$html = getSimpleHTMLDOM($this->getURI(), $header);
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('No results for this query.');
}
@@ -570,7 +555,7 @@ EOD;
}
// No captcha? We can carry on retrieving page contents :)
// First, we check whether the page is public or not
// First, we check wether the page is public or not
$loginForm = $html->find('._585r', 0);
if($loginForm != null) {

View File

@@ -77,11 +77,12 @@ class FicbookBridge extends BridgeAbstract {
$header = array('Accept-Language: en-US');
$html = getSimpleHTMLDOM($this->getURI(), $header);
$html = getSimpleHTMLDOM($this->getURI(), $header)
or returnServerError('Could not request ' . $this->getURI());
$html = defaultLinkTo($html, self::URI);
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments') {
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments' ) {
$this->titleName = $html->find('.fanfic-main-info > h1', 0)->innertext;
}

View File

@@ -0,0 +1,33 @@
<?php
class FierPandaBridge extends BridgeAbstract {
const MAINTAINER = 'snroki';
const NAME = 'Fier Panda Bridge';
const URI = 'http://www.fier-panda.fr/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
public function getIcon() {
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Fier Panda.');
defaultLinkTo($html, static::URI);
foreach($html->find('article') as $article) {
$item = array();
$item['uri'] = $article->find('a', 0)->href;
$item['title'] = $article->find('a', 0)->title;
$this->items[] = $item;
}
}
}

View File

@@ -2,11 +2,11 @@
class FilterBridge extends FeedExpander {
const MAINTAINER = 'Frenzie, ORelio';
const MAINTAINER = 'Frenzie';
const NAME = 'Filter';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Filters a feed of your choice';
const URI = 'https://github.com/RSS-Bridge/rss-bridge';
const URI = 'https://github.com/rss-bridge/rss-bridge';
const PARAMETERS = array(array(
'url' => array(
@@ -14,7 +14,7 @@ class FilterBridge extends FeedExpander {
'required' => true,
),
'filter' => array(
'name' => 'Filter (regular expression)',
'name' => 'Filter item title (regular expression)',
'required' => false,
),
'filter_type' => array(
@@ -22,101 +22,60 @@ class FilterBridge extends FeedExpander {
'type' => 'list',
'required' => false,
'values' => array(
'Keep matching items' => 'permit',
'Hide matching items' => 'block',
'Permit' => 'permit',
'Block' => 'block',
),
'defaultValue' => 'permit',
),
'case_insensitive' => array(
'name' => 'Case-insensitive filter',
'type' => 'checkbox',
'required' => false,
),
'fix_encoding' => array(
'name' => 'Attempt Latin1/UTF-8 fixes when evaluating filter',
'type' => 'checkbox',
'required' => false,
),
'target_title' => array(
'name' => 'Apply filter on title',
'type' => 'checkbox',
'required' => false,
'defaultValue' => 'checked'
),
'target_content' => array(
'name' => 'Apply filter on content',
'type' => 'checkbox',
'required' => false,
),
'target_author' => array(
'name' => 'Apply filter on author',
'type' => 'checkbox',
'required' => false,
),
'title_from_content' => array(
'name' => 'Generate title from content (overwrite existing title)',
'name' => 'Generate title from content',
'type' => 'checkbox',
'required' => false,
),
'length_limit' => array(
'name' => 'Max length analyzed by filter (-1: no limit)',
'type' => 'number',
'required' => false,
'defaultValue' => -1,
),
)
));
protected function parseItem($newItem){
$item = parent::parseItem($newItem);
// Generate title from first 50 characters of content?
if($this->getInput('title_from_content') && array_key_exists('content', $item)) {
$content = str_get_html($item['content']);
$pos = strpos($item['content'], ' ', 50);
$item['title'] = substr($content->plaintext, 0, $pos);
$item['title'] = substr(
$content->plaintext,
0,
$pos
);
if(strlen($content->plaintext) >= $pos) {
$item['title'] .= '...';
}
}
// Build regular expression
$regex = '/' . $this->getInput('filter') . '/';
if($this->getInput('case_insensitive')) {
$regex .= 'i';
}
// Retrieve fields to check
$filter_fields = array();
if($this->getInput('target_title')) {
$filter_fields[] = $item['title'];
}
if($this->getInput('target_content')) {
$filter_fields[] = $item['content'];
}
if($this->getInput('target_author')) {
$filter_fields[] = $item['author'];
}
// Apply filter on item
$keep_item = false;
$length_limit = intval($this->getInput('length_limit'));
foreach($filter_fields as $field) {
if($length_limit > 0) {
$field = substr($field, 0, $length_limit);
switch(true) {
case $this->getFilterType() === 'permit':
if (preg_match($this->getFilter(), $item['title'])) {
return $item;
}
$keep_item |= boolval(preg_match($regex, $field));
if($this->getInput('fix_encoding')) {
$keep_item |= boolval(preg_match($regex, utf8_decode($field)));
$keep_item |= boolval(preg_match($regex, utf8_encode($field)));
break;
case $this->getFilterType() === 'block':
if (!preg_match($this->getFilter(), $item['title'])) {
return $item;
}
break;
}
return null;
}
// Reverse result? (keep everything but matching items)
if($this->getInput('filter_type') === 'block') {
$keep_item = !$keep_item;
}
protected function getFilter(){
return '/' . $this->getInput('filter') . '/';
}
return $keep_item ? $item : null;
protected function getFilterType(){
return $this->getInput('filter_type');
}
public function getURI(){
@@ -125,15 +84,18 @@ class FilterBridge extends FeedExpander {
if(empty($url)) {
$url = parent::getURI();
}
return $url;
}
public function collectData(){
if($this->getInput('url') && substr($this->getInput('url'), 0, 4) !== 'http') {
// just in case someone finds a way to access local files by playing with the url
if($this->getInput('url') && substr($this->getInput('url'), 0, strlen('http')) !== 'http') {
// just in case someone find a way to access local files by playing with the url
returnClientError('The url parameter must either refer to http or https protocol.');
}
$this->collectExpandableDatas($this->getURI());
try{
$this->collectExpandableDatas($this->getURI());
} catch (Exception $e) {
$this->collectExpandableDatas($this->getURI());
}
}
}

View File

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

View File

@@ -14,7 +14,8 @@ class FirstLookMediaTechBridge extends BridgeAbstract {
);
public function collectData() {
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not load content');
if ($this->getInput('projects')) {
$top_projects = $html->find('.PromoList-ul', 0);

View File

@@ -1,185 +0,0 @@
<?php
class FlashbackBridge extends BridgeAbstract
{
const MAINTAINER = 'fatuus';
const NAME = 'Flashback forum';
const URI = 'https://www.flashback.org';
const DESCRIPTION = 'Returns post from forum';
const CACHE_TIMEOUT = 10800; // 3h
const PARAMETERS = array(
'Category' => array(
'c' => array(
'name' => 'Category number',
'type' => 'number',
'exampleValue' => '249',
'required' => true
)
),
'Tag' => array(
'a' => array(
'name' => 'Tag',
'type' => 'text',
'exampleValue' => 'stockholm',
'required' => true
)
),
'Thread' => array(
't' => array(
'name' => 'Thread number',
'type' => 'number',
'exampleValue' => '1420554',
'required' => true
)
),
/*'User' => array(
'u' => array(
'name' => 'User number',
'type' => 'text',
'exampleValue' => 'not working, need login',
'required' => true
)
),*/
'Search string' => array(
's' => array(
'name' => 'Words',
'type' => 'text',
'exampleValue' => 'sök',
'required' => true
),
'type' => array(
'name' => 'Type of search',
'type' => 'list',
'defaultValue' => 'Posts',
'values' => array(
'Posts' => 'posts',
'Subjects' => 'subjects'
)
)
)
);
public function getName()
{
if ($this->getInput('c')) {
$category = $this->getInput('c');
return 'Category ' . $category . ' - Flashback';
} elseif ($this->getInput('a')) {
$tag = $this->getInput('a');
return 'Tag: ' . $tag . ' - Flashback';
} elseif ($this->getInput('t')) {
$thread = $this->getInput('t');
return 'Thread ' . $thread . ' - Flashback';
} elseif ($this->getInput('u')) {
$user = $this->getInput('u');
return 'User ' . $user . ' - Flashback';
} elseif ($this->getInput('s')) {
$search = $this->getInput('s');
return 'Search: ' . $search . ' - Flashback';
}
return self::NAME;
}
public function collectData()
{
if ($this->getInput('c')) {
$page = self::URI . '/f' . $this->getInput('c');
} elseif ($this->getInput('a')) {
$page = self::URI . '/find_threads_by_tag.php?tag=' . $this->getInput('a');
} elseif ($this->getInput('t')) {
$page = self::URI . '/t' . $this->getInput('t');
$page = $page . 's'; // last-page
} elseif ($this->getInput('u')) {
$page = self::URI . '/find_posts_by_user.php?userid=' . $this->getInput('u');
} elseif ($this->getInput('s')) {
if ($this->getInput('type') == 'posts') {
$page = self::URI . '/sok/?query=' . $this->getInput('s') . '&search_post=1&sp=1&so=pd';
} else {
$page = self::URI . '/sok/?query=' . $this->getInput('s') . '&search_post=0&sp=1&so=pd';
}
}
$html = getSimpleHTMLDOM($page);
if ($this->getInput('c') || $this->getInput('a')) {
$category = $this->getInput('c');
$array = $html->find('table#threadslist tbody tr');
foreach ($array as $key => $element) {
$item = array();
$item['uri'] = self::URI . $element->find('td.td_title a', 0)->href;
$item['title'] = trim(utf8_encode($element->find('td.td_title a', 0)->innertext));
$item['author'] = trim(utf8_encode(
$element->find('td.td_title span.thread-poster span', 0)->innertext)
);
$timestamp = $element->find('td.td_last_post div', 0);
if (isset($timestamp->plaintext)) {
$item['timestamp'] = strtotime(str_replace(array('Ig&aring;r', 'Idag'),
array('yesterday', 'today'), trim($timestamp->plaintext)));
}
$item['content'] = $item['title'] . '<br />' . trim(preg_replace('/\t+/', '',
$element->find('td.td_replies', 0)->innertext));
$item['uid'] = preg_split('/(\/)/', $element->find('td.td_title a', 0)->href)[1];
$this->items[] = $item;
}
} elseif ($this->getInput('t')) {
$tags = $html->find('div.hidden-xs a.tag');
$array = $html->find('div.post');
foreach ($array as $key => $element) {
$item = array();
$item['uri_post'] = self::URI . $element->find('div.post-heading a', 2)->href;
$item['uri'] = self::URI . '/' . preg_split('/(\/s)/', $item['uri_post'])[1] . '#' .
preg_split('/(\/s)/', $item['uri_post'])[1];
$item['uri_thread'] = $page;
$item['author'] = utf8_encode($element->find('div.post-user ul li', 0)->innertext);
$item['author_link'] = self::URI . $element->find('div.post-user ul li a', 0)->href;
$item['post_nr'] = $element->find('div.post-heading a strong', 0)->innertext;
$item['timestamp'] = strtotime(
str_replace(
array('Ig&aring;r', 'Idag'), array('yesterday', 'today'),
current(explode("\t", str_replace("\t\t", "\t", trim(
$element->find('div.post-heading', 0)->plaintext)
)))
)
);
if ($element->find('div.smallfont strong', 0)) {
$item['title'] = trim(utf8_encode($element->find('div.smallfont strong', 0)->innertext));
}
if (empty($item['title'])) {
$item['title'] = date('D j M y H:i', $item['timestamp']);
}
$item['content'] = trim(preg_replace('/\t+/', '', $element->find('div.post_message', 0)));
$item['uid'] = preg_split('/(\#|\/)/', $element->find('div.post-heading a', 2)->href)[1];
foreach ($tags as $tag_key => $tag) {
$item['categories'][] = trim(utf8_encode($tag->innertext));
}
$this->items[] = $item;
}
// } elseif ( $this->getInput('u') ) {
} elseif ($this->getInput('s')) {
$array = $html->find('div.post');
foreach ($array as $key => $element) {
$item = array();
$item['uri'] = self::URI . $element->find('div.post-body a', 0)->href;
$item['uri_thread'] = $page . $element->find('div.post-heading a', 0)->href . 's';
$item['author'] = $element->find('div.post-body a', 1)->innertext;
$item['author_link'] = self::URI . $element->find('div.post-body a', 1)->href;
$time = preg_split('/(\>)/', $element->find('div.post-heading', 0)->innertext);
$item['timestamp'] = strtotime(trim(end($time)));
$item['title'] = trim(utf8_encode($element->find('div.post-body strong', 0)->innertext));
if (empty($item['title'])) {
$item['title'] = date('D j M y H:i', $item['timestamp']);
}
$item['datetime'] = (trim(end($time)));
$item['categories'][] = trim(utf8_encode($element->find('div.post-heading a', 0)->innertext));
$item['content'] = trim(preg_replace('/\t+/', '', $element->find('div.post_message', 0)));
$item['uid'] = preg_split('/(\#|\/)/', $element->find('div.post-body a', 0)->href)[1];
$this->items[] = $item;
}
}
}
}

View File

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

View File

@@ -7,7 +7,8 @@ class FootitoBridge extends BridgeAbstract {
const DESCRIPTION = 'Footito';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request Footito.');
foreach($html->find('div.post') as $element) {
$item = array();

View File

@@ -1,68 +0,0 @@
<?php
class Formula1Bridge extends BridgeAbstract {
const NAME = 'Formula1 Bridge';
const URI = 'https://formula1.com/';
const DESCRIPTION = 'Returns latest official Formula 1 news';
const MAINTAINER = 'AxorPL';
const API_KEY = 'qPgPPRJyGCIPxFT3el4MF7thXHyJCzAP';
const API_URL = 'https://api.formula1.com/v1/editorial/articles?limit=%u';
const ARTICLE_AUTHOR = 'Formula 1';
const ARTICLE_HTML = '<p>%s</p><a href="%s" target="_blank"><img src="%s" alt="%s" title="%s"></a>';
const ARTICLE_URL = 'https://formula1.com/en/latest/article.%s.%s.html';
const LIMIT_MIN = 1;
const LIMIT_DEFAULT = 10;
const LIMIT_MAX = 100;
const PARAMETERS = array(
array(
'limit' => array(
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Number of articles to return',
'exampleValue' => self::LIMIT_DEFAULT,
'defaultValue' => self::LIMIT_DEFAULT
)
)
);
public function collectData() {
$limit = $this->getInput('limit') ?: self::LIMIT_DEFAULT;
$limit = min(self::LIMIT_MAX, max(self::LIMIT_MIN, $limit));
$url = sprintf(self::API_URL, $limit);
$json = json_decode(getContents($url, array('apikey: ' . self::API_KEY)));
if(property_exists($json, 'error')) {
returnServerError($json->message);
}
$list = $json->items;
foreach($list as $article) {
if(property_exists($article->thumbnail, 'caption')) {
$caption = $article->thumbnail->caption;
} else {
$caption = $article->thumbnail->image->title;
}
$item = array();
$item['uri'] = sprintf(self::ARTICLE_URL, $article->slug, $article->id);
$item['title'] = $article->title;
$item['timestamp'] = $article->updatedAt;
$item['author'] = self::ARTICLE_AUTHOR;
$item['enclosures'] = array($article->thumbnail->image->url);
$item['uid'] = $article->id;
$item['content'] = sprintf(
self::ARTICLE_HTML,
$article->metaDescription,
$item['uri'],
$item['enclosures'][0],
$caption,
$caption
);
$this->items[] = $item;
}
}
}

View File

@@ -29,7 +29,8 @@ class FourchanBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request 4chan, thread not found');
foreach($html->find('div.postContainer') as $element) {
$item = array();

View File

@@ -28,7 +28,8 @@ class FurAffinityUserBridge extends BridgeAbstract {
$cookies = self::login();
$url = self::URI . '/gallery/' . $this->getInput('searchUsername');
$html = getSimpleHTMLDOM($url, $cookies);
$html = getSimpleHTMLDOM($url, $cookies)
or returnServerError('Could not load the user\'s galary page.');
$submissions = $html->find('section[id=gallery-gallery]', 0)->find('figure');
foreach($submissions as $submission) {

Some files were not shown because too many files have changed in this diff Show More