mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 13:34:11 +02:00
Compare commits
222 Commits
2020-02-26
...
2021-04-25
Author | SHA1 | Date | |
---|---|---|---|
|
b24b5ed3ee | ||
|
f06a8ae307 | ||
|
4f7ef212b7 | ||
|
13e9a96cf3 | ||
|
00a24a98be | ||
|
76c38332ee | ||
|
65be209a47 | ||
|
146639ffc9 | ||
|
e1c19461ca | ||
|
ff0c7a9013 | ||
|
b754d14698 | ||
|
3423b3bbe1 | ||
|
5966cc0a9c | ||
|
579bfa669c | ||
|
d61871a45e | ||
|
0c8fabeb11 | ||
|
40c84b5dc3 | ||
|
6f75d07456 | ||
|
b4f809aa44 | ||
|
bcecd70df7 | ||
|
a6c0874b9a | ||
|
9e6f063cfd | ||
|
f904353fd2 | ||
|
3aafd44079 | ||
|
75b85f61e7 | ||
|
07e1e8497c | ||
|
700813e924 | ||
|
5c011c8d90 | ||
|
8d0d08a4d8 | ||
|
55548dcb5f | ||
|
0217b270a7 | ||
|
2ed34f5ebe | ||
|
2448ed41c9 | ||
|
b25674b3a0 | ||
|
2ce1a6365b | ||
|
30aeeb2a0c | ||
|
c294a652a3 | ||
|
a5f2175531 | ||
|
569276f4ef | ||
|
687eb728d4 | ||
|
0521ba5873 | ||
|
3d642971c0 | ||
|
8f086169cc | ||
|
ce34e7eb89 | ||
|
ee5d190391 | ||
|
98352845a1 | ||
|
9e58735b01 | ||
|
771b851b52 | ||
|
809343ed06 | ||
|
e9424f6a08 | ||
|
e5846c03ba | ||
|
6224fbb6a2 | ||
|
eab575dc9d | ||
|
b56637c833 | ||
|
005b22701d | ||
|
43b7621f45 | ||
|
ea289a0cea | ||
|
43acb555e0 | ||
|
3b7e61fb55 | ||
|
fbbd6a02c6 | ||
|
3534193032 | ||
|
81fc8c89d4 | ||
|
ea1de07fe5 | ||
|
2de5ce8387 | ||
|
28f9215913 | ||
|
f927781750 | ||
|
e128ce807a | ||
|
6b870f0c3e | ||
|
5ed161943c | ||
|
1edec1aa45 | ||
|
3c285d50ec | ||
|
3aae00b56a | ||
|
21798e8228 | ||
|
3226a5e31e | ||
|
59bbc9d2e7 | ||
|
2ddd357a62 | ||
|
c302bca1e6 | ||
|
0b3082609d | ||
|
810a2503c9 | ||
|
56b2c516e4 | ||
|
fc81bed717 | ||
|
56eb829a66 | ||
|
7705d097e3 | ||
|
be9df41e07 | ||
|
1e75f9d3d5 | ||
|
0755181555 | ||
|
e6c73a1fe3 | ||
|
5729e069e9 | ||
|
0b494d9c0e | ||
|
c855d5089f | ||
|
6baf64f29e | ||
|
7d4b76be99 | ||
|
ff50e4918c | ||
|
8e4d6d8fdb | ||
|
cc548b16a8 | ||
|
b66026e241 | ||
|
a23d4bd0e6 | ||
|
bde4159a9e | ||
|
3ad138026d | ||
|
d05a8b79fe | ||
|
efe32aad22 | ||
|
0655b3cb39 | ||
|
59082368c7 | ||
|
c8b2c1bf74 | ||
|
b48bc77c22 | ||
|
6af87b2f32 | ||
|
93cdf5e342 | ||
|
164b407f28 | ||
|
2714c3d816 | ||
|
364b5282a3 | ||
|
5e4f3c351e | ||
|
a332a5a414 | ||
|
45e2f385b3 | ||
|
0a1ff10a52 | ||
|
645a8f62c6 | ||
|
64ec488f70 | ||
|
7b6ff78623 | ||
|
82acbbb421 | ||
|
84d5daaa03 | ||
|
712f60e910 | ||
|
55015f80cf | ||
|
f90c6b5bb9 | ||
|
ff98efe8dc | ||
|
fe166d0216 | ||
|
d3455dd18a | ||
|
47dc26c775 | ||
|
3df2de4c6f | ||
|
01985b7af7 | ||
|
80cc88ba78 | ||
|
2bb99c4448 | ||
|
3a29347e60 | ||
|
d299adb827 | ||
|
cf606a3a6b | ||
|
6c244f4d9b | ||
|
d6f277d029 | ||
|
ab8e89a97f | ||
|
747bb6ad9c | ||
|
d33e090fe1 | ||
|
fec52418d5 | ||
|
bb51a0d212 | ||
|
68dd2d745f | ||
|
46abc18e87 | ||
|
e00bbe353f | ||
|
c21a805cb4 | ||
|
3b36c413e5 | ||
|
94576c3053 | ||
|
25cff9c07b | ||
|
07c71b3b36 | ||
|
859053ef7a | ||
|
73287f536b | ||
|
0b1e592a5e | ||
|
ef54a78430 | ||
|
4b8c3b9d36 | ||
|
c642652fea | ||
|
f0e6298cab | ||
|
45e247b9d0 | ||
|
66a009b8fb | ||
|
efd1abfab1 | ||
|
8b173b8874 | ||
|
90e9c9962a | ||
|
01cc32a0cc | ||
|
dc36b425cd | ||
|
268ddf1382 | ||
|
8144488a9e | ||
|
062dd7f8a5 | ||
|
be089702f0 | ||
|
c71fad4a4a | ||
|
5be251a66e | ||
|
7709b8d662 | ||
|
f5916a2f74 | ||
|
a33088ca99 | ||
|
78facbcb83 | ||
|
d5a75a2545 | ||
|
25698d182c | ||
|
9e74cc64ed | ||
|
78298385d0 | ||
|
976445b490 | ||
|
3ad126cdf2 | ||
|
e87b868307 | ||
|
23c61f5f84 | ||
|
22a01f1093 | ||
|
98ff5a095c | ||
|
e4c4ae8245 | ||
|
124631df73 | ||
|
06891ae35f | ||
|
c4422bdbb5 | ||
|
a1dd98ff82 | ||
|
25f0d3b877 | ||
|
9a66227a79 | ||
|
8047041963 | ||
|
fa74d3728b | ||
|
8233497611 | ||
|
71745116e1 | ||
|
36fc4822dd | ||
|
868d3f600d | ||
|
f4affe1833 | ||
|
63a4db7e86 | ||
|
f48909b84e | ||
|
ca88096f1f | ||
|
1044952987 | ||
|
119f4bdec5 | ||
|
e617d9f728 | ||
|
5a43db4fb5 | ||
|
badb5313b7 | ||
|
5eeda8dd52 | ||
|
413ae3cef6 | ||
|
604d527ac7 | ||
|
cccd390b0f | ||
|
223337d62d | ||
|
066e42e99a | ||
|
fbfc82b0b7 | ||
|
00dd81a8aa | ||
|
e0ac9972ee | ||
|
f2de5aecc7 | ||
|
0fd7021030 | ||
|
3ec32bb6c2 | ||
|
ec7ef8f502 | ||
|
7b73f3217f | ||
|
7c71377af0 | ||
|
c2559ff71f | ||
|
366d2d66b3 | ||
|
7b63da522f |
35
.github/workflows/lint.yml
vendored
Normal file
35
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
phpcs:
|
||||
runs-on: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpcs
|
||||
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
||||
|
||||
phpcompatibility:
|
||||
runs-on: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['5.6', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
|
||||
- run: composer global require phpcompatibility/php-compatibility
|
||||
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p
|
47
.github/workflows/tests.yml
vendored
Normal file
47
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
phpunit6:
|
||||
runs-on: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.0', '7.1', '7.2']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require phpunit/phpunit ^6
|
||||
- run: phpunit --configuration=phpunit.xml --include-path=lib/
|
||||
|
||||
phpunit7:
|
||||
runs-on: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.1', '7.2', '7.3']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require phpunit/phpunit ^7
|
||||
- run: phpunit --configuration=phpunit.xml --include-path=lib/
|
||||
|
||||
phpunit8:
|
||||
runs-on: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require phpunit/phpunit ^8
|
||||
- run: phpunit --configuration=phpunit.xml --include-path=lib/
|
46
.travis.yml
46
.travis.yml
@@ -1,46 +0,0 @@
|
||||
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
|
18
Dockerfile
18
Dockerfile
@@ -3,20 +3,16 @@ FROM php:7-apache
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
|
||||
&& apt-get --yes update && apt-get --yes install libxml2-dev zlib1g-dev libmemcached-dev \
|
||||
&& docker-php-ext-install -j$(nproc) simplexml \
|
||||
&& apt-get --yes update \
|
||||
&& 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 \
|
||||
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
|
||||
&& 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
|
||||
|
||||
RUN curl https://codeload.github.com/php-memcached-dev/php-memcached/tar.gz/v3.1.5 --output /tmp/php-memcached.tar.gz \
|
||||
&& mkdir -p /usr/src/php/ext \
|
||||
&& tar xzvf /tmp/php-memcached.tar.gz -C /usr/src/php/ext \
|
||||
&& mv /usr/src/php/ext/php-memcached-3.1.5 /usr/src/php/ext/memcached \
|
||||
&& cd /usr/src/php/ext/memcached \
|
||||
&& docker-php-ext-configure /usr/src/php/ext/memcached --disable-memcached-sasl \
|
||||
&& docker-php-ext-install /usr/src/php/ext/memcached \
|
||||
&& rm -rf /usr/src/php/ext/memcached
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
63
README.md
63
README.md
@@ -1,6 +1,6 @@
|
||||

|
||||
===
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://tracker.debian.org/pkg/rss-bridge) [](https://www.gnu.org/software/guix/packages/R/) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://tracker.debian.org/pkg/rss-bridge) [](https://www.gnu.org/software/guix/packages/R/) [](https://github.com/RSS-Bridge/rss-bridge/actions) [](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.
|
||||
|
||||
@@ -65,6 +65,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
||||
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
||||
- [`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)
|
||||
@@ -109,34 +110,41 @@ We are RSS-Bridge community, a group of developers continuing the project initia
|
||||
Use this script to generate the list automatically (using the GitHub API):
|
||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
-->
|
||||
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [86423355844265459587182778](https://github.com/86423355844265459587182778)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [akirk](https://github.com/akirk)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alex73](https://github.com/alex73)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [arnd-s](https://github.com/arnd-s)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [AxorPL](https://github.com/AxorPL)
|
||||
* [ayacoo](https://github.com/ayacoo)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [azdkj532](https://github.com/azdkj532)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [Binnette](https://github.com/Binnette)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [Chouchen](https://github.com/Chouchen)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cn-tools](https://github.com/cn-tools)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [csisoap](https://github.com/csisoap)
|
||||
* [cyberjacob](https://github.com/cyberjacob)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [dawidsowa](https://github.com/dawidsowa)
|
||||
* [DevonHess](https://github.com/DevonHess)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [DJCrashdummy](https://github.com/DJCrashdummy)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
@@ -144,32 +152,47 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
* [dominik-th](https://github.com/dominik-th)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [drego85](https://github.com/drego85)
|
||||
* [drklee3](https://github.com/drklee3)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [fanch317](https://github.com/fanch317)
|
||||
* [fivefilters](https://github.com/fivefilters)
|
||||
* [floviolleau](https://github.com/floviolleau)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [ggiessen](https://github.com/ggiessen)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [gloony](https://github.com/gloony)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [gsantner](https://github.com/gsantner)
|
||||
* [guigot](https://github.com/guigot)
|
||||
* [hollowleviathan](https://github.com/hollowleviathan)
|
||||
* [hpacleb](https://github.com/hpacleb)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [husim0](https://github.com/husim0)
|
||||
* [IceWreck](https://github.com/IceWreck)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jacquesh](https://github.com/jacquesh)
|
||||
* [JasonGhent](https://github.com/JasonGhent)
|
||||
* [jcgoette](https://github.com/jcgoette)
|
||||
* [jdesgats](https://github.com/jdesgats)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [JimDog546](https://github.com/JimDog546)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [johnpc](https://github.com/johnpc)
|
||||
* [killruana](https://github.com/killruana)
|
||||
* [joni1993](https://github.com/joni1993)
|
||||
* [joshcoales](https://github.com/joshcoales)
|
||||
* [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)
|
||||
@@ -178,6 +201,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [liamka](https://github.com/liamka)
|
||||
* [Limero](https://github.com/Limero)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [lorenzos](https://github.com/lorenzos)
|
||||
@@ -188,54 +212,78 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
* [mdemoss](https://github.com/mdemoss)
|
||||
* [melangue](https://github.com/melangue)
|
||||
* [metaMMA](https://github.com/metaMMA)
|
||||
* [mibe](https://github.com/mibe)
|
||||
* [mightymt](https://github.com/mightymt)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [Monocularity](https://github.com/Monocularity)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mschwld](https://github.com/mschwld)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Niehztog](https://github.com/Niehztog)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
||||
* [OliverParoczai](https://github.com/OliverParoczai)
|
||||
* [oratosquilla-oratoria](https://github.com/oratosquilla-oratoria)
|
||||
* [Ololbu](https://github.com/Ololbu)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [otakuf](https://github.com/otakuf)
|
||||
* [Park0](https://github.com/Park0)
|
||||
* [Paroleen](https://github.com/Paroleen)
|
||||
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||
* [pellaeon](https://github.com/pellaeon)
|
||||
* [PeterDaveHello](https://github.com/PeterDaveHello)
|
||||
* [Peterr-K](https://github.com/Peterr-K)
|
||||
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://github.com/pitchoule)
|
||||
* [pmaziere](https://github.com/pmaziere)
|
||||
* [Pofilo](https://github.com/Pofilo)
|
||||
* [prysme01](https://github.com/prysme01)
|
||||
* [Qluxzz](https://github.com/Qluxzz)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [rakoo](https://github.com/rakoo)
|
||||
* [RawkBob](https://github.com/RawkBob)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [ronansalmon](https://github.com/ronansalmon)
|
||||
* [rremizov](https://github.com/rremizov)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [shutosg](https://github.com/shutosg)
|
||||
* [simon816](https://github.com/simon816)
|
||||
* [Simounet](https://github.com/Simounet)
|
||||
* [somini](https://github.com/somini)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
||||
* [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [t0stiman](https://github.com/t0stiman)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [tgkenney](https://github.com/tgkenney)
|
||||
* [thefranke](https://github.com/thefranke)
|
||||
* [ThePadawan](https://github.com/ThePadawan)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [theScrabi](https://github.com/theScrabi)
|
||||
* [thezeroalpha](https://github.com/thezeroalpha)
|
||||
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [xurxof](https://github.com/xurxof)
|
||||
* [yamanq](https://github.com/yamanq)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
* [ymeister](https://github.com/ymeister)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
|
||||
|
||||
Licenses
|
||||
===
|
||||
|
||||
@@ -243,6 +291,7 @@ The source code for RSS-Bridge is [Public Domain](UNLICENSE).
|
||||
|
||||
RSS-Bridge uses third party libraries with their own license:
|
||||
|
||||
* [`Parsedown`](https://github.com/erusev/parsedown) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
* [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
||||
@@ -264,6 +313,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 wilfully destroyed.
|
||||
We are rebuilding bridges you have willfully destroyed.
|
||||
|
||||
Get your shit together: Put RSS/Atom back in.
|
||||
|
@@ -131,6 +131,7 @@ class DisplayAction extends ActionAbstract {
|
||||
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->loadConfiguration();
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
|
57
bridges/ASRockNewsBridge.php
Normal file
57
bridges/ASRockNewsBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class ASRockNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'ASRock News Bridge';
|
||||
const URI = 'https://www.asrock.com';
|
||||
const DESCRIPTION = 'Returns latest news articles';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array();
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp')
|
||||
or returnServerError('Could not request: ' . self::URI . '/news/index.asp');
|
||||
|
||||
$html = defaultLinkTo($html, self::URI . '/news/');
|
||||
|
||||
foreach($html->find('div.inner > a') as $index => $a) {
|
||||
$item = array();
|
||||
|
||||
$articlePath = $a->href;
|
||||
|
||||
$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('h5', 0)->innertext;
|
||||
|
||||
$contents->find('h5', 0)->outertext = '';
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['enclosures'][] = $a->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDate($text) {
|
||||
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
|
||||
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match($dateRegex, $text, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
54
bridges/AirBreizhBridge.php
Normal file
54
bridges/AirBreizhBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class AirBreizhBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'fanch317';
|
||||
const NAME = 'Air Breizh';
|
||||
const URI = 'https://www.airbreizh.asso.fr/';
|
||||
const DESCRIPTION = 'Returns newests publications on Air Breizh';
|
||||
const PARAMETERS = array(
|
||||
'Publications' => array(
|
||||
'theme' => array(
|
||||
'name' => 'Thematique',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tout' => '',
|
||||
'Rapport d\'activite' => 'rapport-dactivite',
|
||||
'Etude' => 'etudes',
|
||||
'Information' => 'information',
|
||||
'Autres documents' => 'autres-documents',
|
||||
'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa',
|
||||
'Transport' => 'transport'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('article') as $article) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
// Author
|
||||
$item['author'] = 'Air Breizh';
|
||||
// Image
|
||||
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
|
||||
// Content preview
|
||||
$item['content'] = '<img src="' . $imagelink . '" />
|
||||
<br/>'
|
||||
. $article->find('.card__text', 0)->plaintext;
|
||||
// URL
|
||||
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
// ID
|
||||
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
74
bridges/AlbionOnlineBridge.php
Normal file
74
bridges/AlbionOnlineBridge.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
class AlbionOnlineBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Albion Online Changelog';
|
||||
const MAINTAINER = 'otakuf';
|
||||
const URI = 'https://albiononline.com';
|
||||
const DESCRIPTION = 'Returns the changes made to the Albion Online';
|
||||
const CACHE_TIMEOUT = 3600; // 60min
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5,
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Deutsch' => 'de',
|
||||
'Polski' => 'pl',
|
||||
'Français' => 'fr',
|
||||
'Русский' => 'ru',
|
||||
'Português' => 'pt',
|
||||
'Español' => 'es',
|
||||
),
|
||||
'title' => 'Language of changelog posts',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full changelog',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable to receive the full changelog post for each item'
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
$api = 'https://albiononline.com/';
|
||||
// Example: https://albiononline.com/en/changelog/1/5
|
||||
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Unable to get changelog data from "' . $url . '"!');
|
||||
|
||||
foreach ($html->find('li') as $data) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
|
||||
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
|
||||
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
|
||||
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
|
||||
//$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['author'] = 'albiononline.com';
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullChangelog($item['uri']);
|
||||
} else {
|
||||
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
|
||||
// Just use title, no info at all or use title and date, see above
|
||||
$item['content'] = $item['title'];
|
||||
}
|
||||
$item['uid'] = hash('sha256', $item['title']);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullChangelog($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;
|
||||
}
|
||||
}
|
@@ -85,7 +85,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('div[class=col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
|
@@ -32,6 +32,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'Sweden' => 'se',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
|
@@ -3,7 +3,9 @@ class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Anidex';
|
||||
const URI = 'https://anidex.info/';
|
||||
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe
|
||||
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
|
||||
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
@@ -108,7 +110,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
$search_url = self::URI . '?s=upload_timestamp&o=desc';
|
||||
$search_url = self::ALTERNATE_URI . '?s=upload_timestamp&o=desc';
|
||||
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
|
||||
@@ -131,8 +133,16 @@ class AnidexBridge extends BridgeAbstract {
|
||||
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
|
||||
}
|
||||
|
||||
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
|
||||
$headers = array('Host: ' . self::ALTERNATE_HOST);
|
||||
|
||||
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
|
||||
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
|
||||
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
|
||||
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url, array(), $opt)
|
||||
$html = getSimpleHTMLDOM($search_url, $headers, $opt)
|
||||
or returnServerError('Could not request Anidex: ' . $search_url);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
@@ -156,10 +166,11 @@ class AnidexBridge extends BridgeAbstract {
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_browse_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
|
||||
@@ -191,7 +202,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['uri'] = $item_browse_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
|
@@ -102,7 +102,6 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item_description = defaultLinkTo($item_description, self::URI);
|
||||
$item_description = str_replace("\r", '', $item_description);
|
||||
$item_description = str_replace("\n", '', $item_description);
|
||||
$item_description = utf8_encode($item_description);
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
|
@@ -20,6 +20,8 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
private $title;
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
@@ -27,6 +29,8 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
|
||||
$imgSize = $this->getInput('imgSize');
|
||||
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
|
||||
// Grab the json data from the page
|
||||
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
|
||||
$html = strstr($html, '{');
|
||||
@@ -59,4 +63,8 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
return $a['timestamp'] < $b['timestamp'];
|
||||
});
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -77,110 +77,69 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
$model_url = self::URI . $this->getInput('url');
|
||||
|
||||
// Get the session cookies and the form token
|
||||
$this->getInitialParameters($model_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');
|
||||
|
||||
// Build the form
|
||||
$post_data = array(
|
||||
'form[energy]' => $this->getInput('energy'),
|
||||
'form[transmission]' => $this->getInput('transmission'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[_token]' => $this->token
|
||||
);
|
||||
|
||||
// Set the Form request content type
|
||||
// Set the header 'X-Requested-With' like the website does it
|
||||
$header = array(
|
||||
'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
|
||||
);
|
||||
|
||||
// Set the curl options (POST query and content, and session cookies
|
||||
$curl_opts = array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($post_data),
|
||||
CURLOPT_COOKIE => $this->cookies
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
);
|
||||
|
||||
// Get the JSON content of the form
|
||||
$json = getContents($model_url, $header, $curl_opts)
|
||||
$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->content);
|
||||
$html = str_get_html($data->results);
|
||||
|
||||
// Go through every finisha of the model
|
||||
$list = $html->find('h3');
|
||||
foreach ($list as $finish) {
|
||||
$finish_name = $finish->plaintext;
|
||||
$motorizations = $finish->next_sibling()->find('li');
|
||||
foreach ($motorizations as $element) {
|
||||
$image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'};
|
||||
$serie = $element->find('span[class=model]', 0)->plaintext;
|
||||
$url = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
if ($element->find('span[class*=block-product-nbModel]', 0) != null) {
|
||||
$availability = 'En Stock';
|
||||
} else {
|
||||
$availability = 'Sur commande';
|
||||
}
|
||||
$discount_html = $element->find('span[class*=tag--promo]', 0);
|
||||
if ($discount_html != null) {
|
||||
$discount = $discount_html->plaintext;
|
||||
} else {
|
||||
$discount = 'inconnue';
|
||||
}
|
||||
$price = $element->find('span[class=price red h1]', 0)->plaintext;
|
||||
$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>';
|
||||
// Go through every car of the model
|
||||
$list = $html->find('div[class=car-card]');
|
||||
foreach ($list as $car) {
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session cookie and the form token
|
||||
*
|
||||
* @param string $pageURL The URL from which to get the values
|
||||
*/
|
||||
private function getInitialParameters($pageURL) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $pageURL);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$data = curl_exec($ch);
|
||||
|
||||
// Separate the response header and the content
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
$content = substr($data, $headerSize);
|
||||
curl_close($ch);
|
||||
|
||||
// Extract the cookies from the headers
|
||||
$cookies = '';
|
||||
$http_response_header = explode("\r\n", $header);
|
||||
foreach ($http_response_header as $hdr) {
|
||||
if (strpos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
$this->cookies = trim(substr($cookies, 1));
|
||||
$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;
|
||||
|
||||
// Get the token from the content
|
||||
$html = str_get_html($content);
|
||||
$token = $html->find('input[type=hidden][id=form__token]', 0);
|
||||
$this->token = $token->value;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
bridges/AwwwardsBridge.php
Normal file
55
bridges/AwwwardsBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
class AwwwardsBridge extends BridgeAbstract {
|
||||
const NAME = 'Awwwards';
|
||||
const URI = 'https://www.awwwards.com/';
|
||||
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
|
||||
const MAINTAINER = 'Paroleen';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
|
||||
const SITEURI = 'https://www.awwwards.com/sites/';
|
||||
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
|
||||
|
||||
private $sites = array();
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.awwwards.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function fetchSites() {
|
||||
Debug::log('Fetching all sites');
|
||||
$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) {
|
||||
$decode = html_entity_decode($site->attr['data-model'],
|
||||
ENT_QUOTES, 'utf-8');
|
||||
$decode = json_decode($decode, true);
|
||||
$this->sites[] = $decode;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->fetchSites();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach($this->sites as $site) {
|
||||
$item = array();
|
||||
$item['title'] = $site['title'];
|
||||
$item['timestamp'] = $site['createdAt'];
|
||||
$item['categories'] = $site['tags'];
|
||||
|
||||
$item['content'] = '<img src="'
|
||||
. self::ASSETSURI
|
||||
. $site['images']['thumbnail']
|
||||
. '">';
|
||||
$item['uri'] = self::SITEURI . $site['slug'];
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -125,9 +125,11 @@ class BandcampBridge extends BridgeAbstract {
|
||||
case 'By album':
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
|
||||
|
||||
$titleElement = $html->find('head meta[name=title]', 0)
|
||||
or returnServerError('Unable to find title on: ' . $this->getURI());
|
||||
$this->feedName = $titleElement->content;
|
||||
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);
|
||||
}
|
||||
|
||||
$regex = '/band_id=(\d+)/';
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
|
@@ -19,13 +19,11 @@ 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);
|
||||
// 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
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($item['uri']);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$item['content'] = $html->find('div.texte', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
29
bridges/BleepingComputerBridge.php
Normal file
29
bridges/BleepingComputerBridge.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
class BleepingComputerBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'csisoap';
|
||||
const NAME = 'Bleeping Computer';
|
||||
const URI = 'https://www.bleepingcomputer.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$article_html = getSimpleHTMLDOMCached($item['uri']);
|
||||
if(!$article_html) {
|
||||
$item['content'] .= '<p><em>Could not request ' . $this->getName() . ': ' . $item['uri'] . '</em></p>';
|
||||
return $item;
|
||||
}
|
||||
|
||||
$article_content = $article_html->find('div.articleBody', 0)->innertext;
|
||||
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
|
||||
$item['content'] = trim($article_content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$feed = static::URI . 'feed/';
|
||||
$this->collectExpandableDatas($feed);
|
||||
}
|
||||
}
|
60
bridges/BlizzardNewsBridge.php
Normal file
60
bridges/BlizzardNewsBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class BlizzardNewsBridge extends XPathAbstract {
|
||||
|
||||
const NAME = 'Blizzard News';
|
||||
const URI = 'https://news.blizzard.com';
|
||||
const DESCRIPTION = 'Blizzard (game company) newsfeed';
|
||||
const MAINTAINER = 'Niehztog';
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'locale' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Deutsch' => 'de-de',
|
||||
'English (EU)' => 'en-gb',
|
||||
'English (US)' => 'en-us',
|
||||
'Español (EU)' => 'es-es',
|
||||
'Español (AL)' => 'es-mx',
|
||||
'Français' => 'fr-fr',
|
||||
'Italiano' => 'it-it',
|
||||
'日本語' => 'ja-jp',
|
||||
'한국어' => 'ko-kr',
|
||||
'Polski' => 'pl-pl',
|
||||
'Português (AL)' => 'pt-br',
|
||||
'Русский' => 'ru-ru',
|
||||
'ภาษาไทย' => 'th-th',
|
||||
'简体中文' => 'zh-cn',
|
||||
'繁體中文' => 'zh-tw'
|
||||
),
|
||||
'defaultValue' => 'en-us',
|
||||
'title' => 'Select your language'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
|
||||
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
|
||||
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
|
||||
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
|
||||
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
|
||||
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
|
||||
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
|
||||
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
|
||||
const SETTING_FIX_ENCODING = true;
|
||||
|
||||
/**
|
||||
* Source Web page URL (should provide either HTML or XML content)
|
||||
* @return string
|
||||
*/
|
||||
protected function getSourceUrl(){
|
||||
|
||||
$locale = $this->getInput('locale');
|
||||
if('zh-cn' === $locale) {
|
||||
return 'https://cn.news.blizzard.com';
|
||||
}
|
||||
return 'https://news.blizzard.com/' . $locale;
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ class BrutBridge extends BridgeAbstract {
|
||||
'Entertainment' => 'entertainment',
|
||||
'Sports' => 'sport',
|
||||
'Nature' => 'nature',
|
||||
'Health' => 'health',
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
),
|
||||
@@ -26,6 +27,7 @@ class BrutBridge extends BridgeAbstract {
|
||||
'United States' => 'us',
|
||||
'United Kingdom' => 'uk',
|
||||
'France' => 'fr',
|
||||
'Spain' => 'es',
|
||||
'India' => 'in',
|
||||
'Mexico' => 'mx',
|
||||
),
|
||||
|
219
bridges/BukowskisBridge.php
Executable file
219
bridges/BukowskisBridge.php
Executable file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
class BukowskisBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bukowskis';
|
||||
const URI = 'https://www.bukowskis.com';
|
||||
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
|
||||
const MAINTAINER = 'Qluxzz';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All categories' => '',
|
||||
'Art' => array(
|
||||
'All' => 'art',
|
||||
'Classic Art' => 'art.classic-art',
|
||||
'Classic Finnish Art' => 'art.classic-finnish-art',
|
||||
'Classic Swedish Art' => 'art.classic-swedish-art',
|
||||
'Contemporary' => 'art.contemporary',
|
||||
'Modern Finnish Art' => 'art.modern-finnish-art',
|
||||
'Modern International Art' => 'art.modern-international-art',
|
||||
'Modern Swedish Art' => 'art.modern-swedish-art',
|
||||
'Old Masters' => 'art.old-masters',
|
||||
'Other' => 'art.other',
|
||||
'Photographs' => 'art.photographs',
|
||||
'Prints' => 'art.prints',
|
||||
'Sculpture' => 'art.sculpture',
|
||||
'Swedish Old Masters' => 'art.swedish-old-masters',
|
||||
),
|
||||
'Asian Ceramics & Works of Art' => array(
|
||||
'All' => 'asian-ceramics-works-of-art',
|
||||
'Other' => 'asian-ceramics-works-of-art.other',
|
||||
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
|
||||
),
|
||||
'Books & Manuscripts' => array(
|
||||
'All' => 'books-manuscripts',
|
||||
'Books' => 'books-manuscripts.books',
|
||||
),
|
||||
'Carpets, rugs & textiles' => array(
|
||||
'All' => 'carpets-rugs-textiles',
|
||||
'European' => 'carpets-rugs-textiles.european',
|
||||
'Oriental' => 'carpets-rugs-textiles.oriental',
|
||||
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
|
||||
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
|
||||
),
|
||||
'Ceramics & porcelain' => array(
|
||||
'All' => 'ceramics-porcelain',
|
||||
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
|
||||
'European' => 'ceramics-porcelain.european',
|
||||
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
|
||||
'Scandinavian' => 'ceramics-porcelain.scandinavian',
|
||||
),
|
||||
'Collectibles' => array(
|
||||
'All' => 'collectibles',
|
||||
'Advertising & Retail' => 'collectibles.advertising-retail',
|
||||
'Memorabilia' => 'collectibles.memorabilia',
|
||||
'Movies & music' => 'collectibles.movies-music',
|
||||
'Other' => 'collectibles.other',
|
||||
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
|
||||
'Technica & Nautica' => 'collectibles.technica-nautica',
|
||||
'Toys' => 'collectibles.toys',
|
||||
),
|
||||
'Design' => array(
|
||||
'All' => 'design',
|
||||
'Art glass' => 'design.art-glass',
|
||||
'Furniture' => 'design.furniture',
|
||||
'Other' => 'design.other',
|
||||
),
|
||||
'Folk art' => array(
|
||||
'All' => 'folk-art',
|
||||
'All categories' => 'lots',
|
||||
),
|
||||
'Furniture' => array(
|
||||
'All' => 'furniture',
|
||||
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
|
||||
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
|
||||
'Chairs' => 'furniture.chairs',
|
||||
'Garden furniture' => 'furniture.garden-furniture',
|
||||
'Mirrors' => 'furniture.mirrors',
|
||||
'Other' => 'furniture.other',
|
||||
'Shelves & Book cases' => 'furniture.shelves-book-cases',
|
||||
'Tables' => 'furniture.tables',
|
||||
),
|
||||
'Glassware' => array(
|
||||
'All' => 'glassware',
|
||||
'Glassware' => 'glassware.glassware',
|
||||
'Other' => 'glassware.other',
|
||||
),
|
||||
'Jewellery' => array(
|
||||
'All' => 'jewellery',
|
||||
'Bracelets' => 'jewellery.bracelets',
|
||||
'Brooches' => 'jewellery.brooches',
|
||||
'Earrings' => 'jewellery.earrings',
|
||||
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
|
||||
'Other' => 'jewellery.other',
|
||||
'Rings' => 'jewellery.rings',
|
||||
),
|
||||
'Lighting' => array(
|
||||
'All' => 'lighting',
|
||||
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
|
||||
'Ceiling lights' => 'lighting.ceiling-lights',
|
||||
'Chandeliers' => 'lighting.chandeliers',
|
||||
'Floor lights' => 'lighting.floor-lights',
|
||||
'Other' => 'lighting.other',
|
||||
'Table lights' => 'lighting.table-lights',
|
||||
'Wall lights' => 'lighting.wall-lights',
|
||||
),
|
||||
'Militaria' => array(
|
||||
'All' => 'militaria',
|
||||
'Honors & Medals' => 'militaria.honors-medals',
|
||||
'Other militaria' => 'militaria.other-militaria',
|
||||
'Weaponry' => 'militaria.weaponry',
|
||||
),
|
||||
'Miscellaneous' => array(
|
||||
'All' => 'miscellaneous',
|
||||
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
|
||||
'Nickel silver' => 'miscellaneous.nickel-silver',
|
||||
'Oriental' => 'miscellaneous.oriental',
|
||||
'Other' => 'miscellaneous.other',
|
||||
),
|
||||
'Silver' => array(
|
||||
'All' => 'silver',
|
||||
'Candle sticks' => 'silver.candle-sticks',
|
||||
'Cups & Bowls' => 'silver.cups-bowls',
|
||||
'Cutlery' => 'silver.cutlery',
|
||||
'Other' => 'silver.other',
|
||||
),
|
||||
'Timepieces' => array(
|
||||
'All' => 'timepieces',
|
||||
'Other' => 'timepieces.other',
|
||||
'Pocket watches' => 'timepieces.pocket-watches',
|
||||
'Table clocks' => 'timepieces.table-clocks',
|
||||
'Wrist watches' => 'timepieces.wrist-watches',
|
||||
),
|
||||
'Vintage & Fashion' => array(
|
||||
'All' => 'vintage-fashion',
|
||||
'Accessories' => 'vintage-fashion.accessories',
|
||||
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
|
||||
'Clothes' => 'vintage-fashion.clothes',
|
||||
),
|
||||
)
|
||||
),
|
||||
'sort_order' => array(
|
||||
'name' => 'Sort order',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Ending soon' => 'ending',
|
||||
'Most recent' => 'recent',
|
||||
'Most bids' => 'most',
|
||||
'Fewest bids' => 'fewest',
|
||||
'Lowest price' => 'lowest',
|
||||
'Highest price' => 'highest',
|
||||
'Lowest estimate' => 'low',
|
||||
'Highest estimate' => 'high',
|
||||
'Alphabetical' => 'alphabetical',
|
||||
),
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Swedish' => 'sv',
|
||||
'Finnish' => 'fi'
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $title;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$baseUrl = 'https://www.bukowskis.com';
|
||||
$category = $this->getInput('category');
|
||||
$language = $this->getInput('language');
|
||||
$sort_order = $this->getInput('sort_order');
|
||||
|
||||
$url = $baseUrl . '/' . $language . '/lots';
|
||||
|
||||
if ($category)
|
||||
$url = $url . '/category/' . $category;
|
||||
|
||||
if ($sort_order)
|
||||
$url = $url . '/sort/' . $sort_order;
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
|
||||
|
||||
foreach ($html->find('div.c-lot-index-lot') as $lot) {
|
||||
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
|
||||
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
|
||||
$images = json_decode(
|
||||
htmlspecialchars_decode(
|
||||
$lot
|
||||
->find('img.o-aspect-ratio__image', 0)
|
||||
->getAttribute('data-thumbnails')
|
||||
)
|
||||
);
|
||||
|
||||
$this->items[] = array(
|
||||
'title' => $title,
|
||||
'uri' => $baseUrl . $relative_url,
|
||||
'uid' => $lot->getAttribute('data-lot-id'),
|
||||
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
|
||||
'enclosures' => array_slice($images, 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
}
|
84
bridges/CeskaTelevizeBridge.php
Normal file
84
bridges/CeskaTelevizeBridge.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
class CeskaTelevizeBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Česká televize Bridge';
|
||||
const URI = 'https://www.ceskatelevize.cz';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Return newest videos';
|
||||
const MAINTAINER = 'kolarcz';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'url' => array(
|
||||
'name' => 'url to the show',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/dily/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function fixChars($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function getUploadTimeFromString($string) {
|
||||
if (strpos($string, 'dnes') !== false) {
|
||||
return strtotime('today');
|
||||
} elseif (strpos($string, 'včera') !== false) {
|
||||
return strtotime('yesterday');
|
||||
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
|
||||
returnServerError('Could not get date from Česká televize string');
|
||||
}
|
||||
|
||||
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
|
||||
return strtotime($date);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
|
||||
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(dily\/((nove|vysilani)\/)?)?$/';
|
||||
if (!preg_match($validUrl, $url, $match)) {
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$category = isset($match[4]) ? $match[4] : 'nove';
|
||||
$fixedUrl = "{$match[1]}dily/{$category}/";
|
||||
|
||||
$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));
|
||||
if ($category !== 'nove') {
|
||||
$this->feedName .= " ({$category})";
|
||||
}
|
||||
|
||||
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="https:' . $itemThumbnail->getAttribute('src') . '" /><br />'
|
||||
. $this->fixChars($itemContent->plaintext),
|
||||
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return isset($this->feedName) ? $this->feedName : parent::getName();
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -53,6 +53,8 @@ class DarkReadingBridge extends FeedExpander {
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
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);
|
||||
|
@@ -45,24 +45,22 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$articles = $html->find('div.single-article')
|
||||
$articles = $html->find('div.crayons-story')
|
||||
or returnServerError('Could not find articles!');
|
||||
|
||||
foreach($articles as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a[id*=article-link]', 0)->href;
|
||||
$item['title'] = $article->find('h3', 0)->plaintext;
|
||||
$item['title'] = $article->find('h2 > a', 0)->plaintext;
|
||||
|
||||
// i.e. "Charlie Harrington・Sep 21"
|
||||
$item['timestamp'] = strtotime(explode('・', $article->find('h4 a', 0)->plaintext, 2)[1]);
|
||||
$item['author'] = explode('・', $article->find('h4 a', 0)->plaintext, 2)[0];
|
||||
$item['timestamp'] = $article->find('time', 0)->datetime;
|
||||
$item['author'] = $article->find('a.crayons-story__secondary.fw-medium', 0)->plaintext;
|
||||
|
||||
// Profile image
|
||||
$item['enclosures'] = array($article->find('img', 0)->src);
|
||||
@@ -70,7 +68,6 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
|
||||
if($this->getInput('full')) {
|
||||
$fullArticle = $this->getFullArticle($item['uri']);
|
||||
$item['content'] = <<<EOD
|
||||
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
|
||||
<p>{$fullArticle}</p>
|
||||
EOD;
|
||||
} else {
|
||||
@@ -80,11 +77,13 @@ EOD;
|
||||
EOD;
|
||||
}
|
||||
|
||||
$item['categories'] = array_map(function($e){ return $e->plaintext; }, $article->find('div.tags span.tag'));
|
||||
// categories
|
||||
foreach ($article->find('a.crayons-tag') as $tag) {
|
||||
$item['categories'][] = str_replace('#', '', $tag->plaintext);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
@@ -101,6 +100,10 @@ EOD;
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
if ($html->find('div.crayons-article__cover', 0)) {
|
||||
return $html->find('div.crayons-article__cover', 0) . $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
|
||||
return $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
}
|
||||
|
84
bridges/DiarioDeNoticiasBridge.php
Normal file
84
bridges/DiarioDeNoticiasBridge.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
class DiarioDeNoticiasBridge extends BridgeAbstract {
|
||||
const NAME = 'Diário de Notícias (PT)';
|
||||
const URI = 'https://dn.pt';
|
||||
const DESCRIPTION = 'Diário de Notícias (DN.PT)';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
'Tag' => array(
|
||||
'n' => array(
|
||||
'name' => 'Tag Name',
|
||||
'exampleValue' => 'rogerio-casanova',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const MONPT = array(
|
||||
'jan',
|
||||
'fev',
|
||||
'mar',
|
||||
'abr',
|
||||
'mai',
|
||||
'jun',
|
||||
'jul',
|
||||
'ago',
|
||||
'set',
|
||||
'out',
|
||||
'nov',
|
||||
'dez',
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.globalnoticias.pt/dn/common/images/favicons/favicon-128.png';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$name = self::NAME . ' | Tag | ' . $this->getInput('n');
|
||||
break;
|
||||
default:
|
||||
$name = self::NAME;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$url = self::URI . '/tag/' . $this->getInput('n') . '.html';
|
||||
break;
|
||||
default:
|
||||
$url = self::URI;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$archives = self::getURI();
|
||||
$html = getSimpleHTMLDOMCached($archives)
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('.t-am-title', 0);
|
||||
$link = $element->find('a.t-am-text', 0);
|
||||
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
|
||||
$snippet = $element->find('.t-am-lead', 0);
|
||||
if ($snippet) {
|
||||
$item['content'] = $snippet->plaintext;
|
||||
}
|
||||
preg_match('|edicao-do-dia\\/(?P<day>\d\d)-(?P<monpt>\w\w\w)-(?P<year>\d\d\d\d)|', $link->href, $d);
|
||||
if ($d) {
|
||||
$item['timestamp'] = sprintf('%s-%s-%s', $d['year'], array_search($d['monpt'], self::MONPT) + 1, $d['day']);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
166
bridges/DockerHubBridge.php
Normal file
166
bridges/DockerHubBridge.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
class DockerHubBridge extends BridgeAbstract {
|
||||
const NAME = 'Docker Hub Bridge';
|
||||
const URI = 'https://hub.docker.com';
|
||||
const DESCRIPTION = 'Returns new images for a container';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'User Submitted Image' => array(
|
||||
'user' => array(
|
||||
'name' => 'User',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rssbridge',
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rss-bridge',
|
||||
)
|
||||
),
|
||||
'Official Image' => array(
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'postgres',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $apiURL = 'https://hub.docker.com/v2/repositories/';
|
||||
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
|
||||
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// user submitted image
|
||||
if(preg_match($this->imageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'User Submitted Image';
|
||||
$params['user'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
return $params;
|
||||
}
|
||||
|
||||
// official image
|
||||
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Official Image';
|
||||
$params['repo'] = $matches[1];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$json = getContents($this->getApiUrl())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$data = json_decode($json, false);
|
||||
|
||||
foreach ($data->results as $result) {
|
||||
$item = array();
|
||||
|
||||
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
|
||||
|
||||
$item['title'] = $result->name;
|
||||
$item['uid'] = $result->id;
|
||||
$item['uri'] = $this->getTagUrl($result->name);
|
||||
$item['author'] = $result->last_updater_username;
|
||||
$item['timestamp'] = $result->tag_last_pushed;
|
||||
$item['content'] = <<<EOD
|
||||
<Strong>Tag</strong><br>
|
||||
<p>{$result->name}</p>
|
||||
<Strong>Last pushed</strong><br>
|
||||
<p>{$lastPushed}</p>
|
||||
<Strong>Images</strong><br>
|
||||
{$this->getImages($result)}
|
||||
EOD;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo();
|
||||
}
|
||||
|
||||
if ($this->getInput('repo')) {
|
||||
return self::URI . '/r/' . $this->getRepo();
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->getInput('repo')) {
|
||||
return $this->getRepo() . ' - Docker Hub';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->getInput('repo');
|
||||
}
|
||||
|
||||
return $this->getInput('user') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
private function getApiUrl() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
private function getLayerUrl($name, $digest) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
|
||||
$this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
private function getTagUrl($name) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
|
||||
}
|
||||
|
||||
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
|
||||
}
|
||||
|
||||
private function getImages($result) {
|
||||
$html = <<<EOD
|
||||
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
|
||||
EOD;
|
||||
|
||||
foreach ($result->images as $image) {
|
||||
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
|
||||
$id = $this->getShortDigestId($image->digest);
|
||||
|
||||
$html .= <<<EOD
|
||||
<tr>
|
||||
<td><a href="{$layersUrl}">{$id}</a></td>
|
||||
<td>{$image->os}/{$image->architecture}</td>
|
||||
</tr>
|
||||
EOD;
|
||||
}
|
||||
|
||||
return $html . '</tbody></table>';
|
||||
}
|
||||
|
||||
private function getShortDigestId($digest) {
|
||||
$parts = explode(':', $digest);
|
||||
return substr($parts[1], 0, 12);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = $this->loadEmbeddedJsonData($html);
|
||||
@@ -24,19 +24,19 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
$additional_data = $this->findJsonForShot($shot, $json);
|
||||
if ($additional_data === null) {
|
||||
$item['uri'] = self::URI . $shot->find('a', 0)->href;
|
||||
$item['title'] = $shot->find('.dribbble-over strong', 0)->plaintext;
|
||||
$item['title'] = $shot->find('.shot-title', 0)->plaintext;
|
||||
} else {
|
||||
$item['timestamp'] = strtotime($additional_data['published_at']);
|
||||
$item['uri'] = self::URI . $additional_data['path'];
|
||||
$item['title'] = $additional_data['title'];
|
||||
}
|
||||
|
||||
$item['author'] = trim($shot->find('.attribution-user a', 0)->plaintext);
|
||||
$item['author'] = trim($shot->find('.user-information .display-name', 0)->plaintext);
|
||||
|
||||
$description = $shot->find('.comment', 0);
|
||||
$item['content'] = $description === null ? '' : $description->plaintext;
|
||||
|
||||
$preview_path = $shot->find('picture source', 0)->attr['srcset'];
|
||||
$preview_path = $shot->find('figure img', 1)->attr['data-srcset'];
|
||||
$item['content'] .= $this->getImageTag($preview_path, $item['title']);
|
||||
$item['enclosures'] = array($this->getFullSizeImagePath($preview_path));
|
||||
|
||||
@@ -51,10 +51,13 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
foreach($scripts as $script) {
|
||||
if(strpos($script->innertext, 'newestShots') !== false) {
|
||||
// fix single quotes
|
||||
$script->innertext = str_replace('\'', '"', $script->innertext);
|
||||
$script->innertext = preg_replace('/\'(.*)\'(,?)$/im', '"\1"\2', $script->innertext);
|
||||
|
||||
// fix JavaScript JSON (why do they not adhere to the standard?)
|
||||
$script->innertext = preg_replace('/(\w+):/i', '"\1":', $script->innertext);
|
||||
$script->innertext = preg_replace('/^(\s*)(\w+):/im', '\1"\2":', $script->innertext);
|
||||
|
||||
// fix relative dates, so they are recognized by strtotime
|
||||
$script->innertext = preg_replace('/"about ([0-9]+ hours? ago)"(,?)$/im', '"\1"\2', $script->innertext);
|
||||
|
||||
// find beginning of JSON array
|
||||
$start = strpos($script->innertext, '[');
|
||||
@@ -83,7 +86,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
|
||||
private function getImageTag($preview_path, $title){
|
||||
return sprintf(
|
||||
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
|
||||
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>',
|
||||
$this->getFullSizeImagePath($preview_path),
|
||||
$preview_path,
|
||||
$title
|
||||
@@ -91,6 +94,11 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
private function getFullSizeImagePath($preview_path){
|
||||
return str_replace('_1x', '', $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);
|
||||
}
|
||||
}
|
||||
|
@@ -14,17 +14,28 @@ class EconomistBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI . '/latest/')
|
||||
or returnServerError('Could not fetch latest updates form The Economist.');
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
foreach($html->find('div.teaser') as $element) {
|
||||
|
||||
$a = $element->find('a.headline-link', 0);
|
||||
$href = $a->href;
|
||||
|
||||
if (substr($href, 0, 4) != 'http')
|
||||
$href = self::URI . $a->href;
|
||||
|
||||
$a = $element->find('a', 0);
|
||||
$href = self::URI . $a->href;
|
||||
$full = getSimpleHTMLDOMCached($href);
|
||||
$article = $full->find('article', 0);
|
||||
$header = $article->find('span[itemprop="headline"]', 0);
|
||||
$headerimg = $article->find('div[itemprop="image"]', 0)->find('img', 0);
|
||||
$author = $article->find('p[itemprop="byline"]', 0);
|
||||
$time = $article->find('time', 0);
|
||||
$content = $article->find('div[itemprop="text"]', 0);
|
||||
$section = array( $article->find('strong[itemprop="articleSection"]', 0)->plaintext );
|
||||
|
||||
$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);
|
||||
// Author
|
||||
if ($author)
|
||||
$author = substr($author->innertext, 3, strlen($author));
|
||||
else
|
||||
$author = 'The Economist';
|
||||
|
||||
// Remove newsletter subscription box
|
||||
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
|
||||
@@ -40,19 +51,15 @@ class EconomistBridge extends BridgeAbstract {
|
||||
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['title'] = $header->innertext;
|
||||
$item['uri'] = $href;
|
||||
$item['timestamp'] = strtotime($time->datetime);
|
||||
$item['author'] = $author->innertext;
|
||||
$item['author'] = $author;
|
||||
$item['categories'] = $section;
|
||||
|
||||
$item['content'] = '<img style="max-width: 100%" src="'
|
||||
. $a->find('img', 0)->src . '">' . $content->innertext;
|
||||
. $headerimg->src . '">' . $content->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
|
93
bridges/EpicgamesBridge.php
Normal file
93
bridges/EpicgamesBridge.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
class EpicgamesBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Epic Games Store News';
|
||||
const MAINTAINER = 'otakuf';
|
||||
const URI = 'https://www.epicgames.com';
|
||||
const DESCRIPTION = 'Returns the latest posts from epicgames.com';
|
||||
const CACHE_TIMEOUT = 3600; // 60min
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 10,
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'العربية' => 'ar',
|
||||
'Deutsch' => 'de',
|
||||
'Español (Spain)' => 'es-ES',
|
||||
'Español (LA)' => 'es-MX',
|
||||
'Français' => 'fr',
|
||||
'Italiano' => 'it',
|
||||
'日本語' => 'ja',
|
||||
'한국어' => 'ko',
|
||||
'Polski' => 'pl',
|
||||
'Português (Brasil)' => 'pt-BR',
|
||||
'Русский' => 'ru',
|
||||
'ไทย' => 'th',
|
||||
'Türkçe' => 'tr',
|
||||
'简体中文' => 'zh-CN',
|
||||
'繁體中文' => 'zh-Hant',
|
||||
),
|
||||
'title' => 'Language of blog posts',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
$api = 'https://store-content.ak.epicgames.com/api/';
|
||||
|
||||
// Get sticky posts first
|
||||
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog/sticky?locale=ru
|
||||
$urlSticky = $api . $this->getInput('language') . '/content/blog/sticky';
|
||||
// Then get posts
|
||||
// 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)
|
||||
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));
|
||||
|
||||
foreach($decodedData as $key => $value) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $value->url;
|
||||
$item['title'] = $value->title;
|
||||
$item['timestamp'] = $value->date;
|
||||
$item['author'] = 'Epic Games Store';
|
||||
if(!empty($value->author)) {
|
||||
$item['author'] = $value->author;
|
||||
}
|
||||
if(!empty($value->content)) {
|
||||
$item['content'] = defaultLinkTo($value->content, self::URI);
|
||||
}
|
||||
if(!empty($value->image)) {
|
||||
$item['enclosures'][] = $value->image;
|
||||
}
|
||||
$item['uid'] = $value->_id;
|
||||
$item['id'] = $value->_id;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
// Sort data
|
||||
usort($this->items, function ($item1, $item2) {
|
||||
if ($item2['timestamp'] == $item1['timestamp']) {
|
||||
return 0;
|
||||
}
|
||||
return ($item2['timestamp'] < $item1['timestamp']) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Limit data
|
||||
$this->items = array_slice($this->items, 0, $this->getInput('postcount'));
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class ExtremeDownloadBridge extends BridgeAbstract {
|
||||
const NAME = 'Extreme Download';
|
||||
const URI = 'https://www.extreme-down.ninja/';
|
||||
const URI = 'https://www.extreme-down.tv/';
|
||||
const DESCRIPTION = 'Suivi de série sur Extreme Download';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
@@ -81,6 +81,16 @@ 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 = '';
|
||||
|
67
bridges/FM4Bridge.php
Normal file
67
bridges/FM4Bridge.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
class FM4Bridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'joni1993';
|
||||
const NAME = 'FM4 Bridge';
|
||||
const URI = 'https://fm4.orf.at';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Feed for FM4 articles by tags (authors)';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'tag' => array(
|
||||
'name' => 'Tag (author, category, ...)',
|
||||
'title' => 'Tag to retrieve',
|
||||
'exampleValue' => 'musik'
|
||||
),
|
||||
'loadcontent' => array(
|
||||
'name' => 'Load Full Article Content',
|
||||
'title' => 'Retrieve full content of articles (may take longer)',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'pages' => array(
|
||||
'name' => 'Pages',
|
||||
'title' => 'Amount of pages to load',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function getPageData($tag, $page) {
|
||||
if($tag)
|
||||
$uri = self::URI . '/tags/' . $tag;
|
||||
else
|
||||
$uri = self::URI;
|
||||
|
||||
$uri = $uri . '?page=' . $page;
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$page_items = array();
|
||||
|
||||
foreach ($html->find('div[class*=listItem]') as $article) {
|
||||
$item = array();
|
||||
|
||||
$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);
|
||||
|
||||
if ($this->getInput('loadcontent')) {
|
||||
$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));
|
||||
}
|
||||
}
|
||||
}
|
115
bridges/FSecureBlogBridge.php
Normal file
115
bridges/FSecureBlogBridge.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
class FSecureBlogBridge extends BridgeAbstract {
|
||||
const NAME = 'F-Secure Blog';
|
||||
const URI = 'https://blog.f-secure.com';
|
||||
const DESCRIPTION = 'F-Secure Blog';
|
||||
const MAINTAINER = 'simon816';
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'categories' => array(
|
||||
'name' => 'Blog categories',
|
||||
'exampleValue' => 'home-security',
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'oldest_date' => array(
|
||||
'name' => 'Oldest article date',
|
||||
'exampleValue' => '-2 months',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
$lang = $this->getInput('language') or 'en';
|
||||
if ($lang === 'en') {
|
||||
return self::URI;
|
||||
}
|
||||
return self::URI . "/$lang";
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->items = array();
|
||||
$this->seen = array();
|
||||
|
||||
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
|
||||
|
||||
$categories = $this->getInput('categories');
|
||||
if (!empty($categories)) {
|
||||
foreach (explode(',', $categories) as $cat) {
|
||||
if (!empty($cat)) {
|
||||
$this->collectCategory($cat);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
|
||||
|
||||
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
|
||||
$url = parse_url($link->href);
|
||||
if (($pos = strpos($url['path'], '/category/')) !== false) {
|
||||
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
|
||||
$this->collectCategory($cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCategory($category) {
|
||||
$url = $this->getURI() . "/category/$category/";
|
||||
while ($url) {
|
||||
$url = $this->collectListing($url);
|
||||
}
|
||||
}
|
||||
|
||||
// n.b. this relies on articles to be ordered by date so the cutoff works
|
||||
private function collectListing($url) {
|
||||
$html = getSimpleHTMLDOMCached($url, 60 * 60);
|
||||
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
|
||||
|
||||
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$url = $item->getAttribute('data-url');
|
||||
if (!$this->collectArticle($url)) {
|
||||
return null; // Too old, stop collecting
|
||||
}
|
||||
}
|
||||
|
||||
// Point's to 404 for non-english blog
|
||||
// $next = $html->find('link[rel=next]', 0);
|
||||
$next = $html->find('ul.page-numbers a.next', 0);
|
||||
return $next ? $next->href : null;
|
||||
}
|
||||
|
||||
// Returns a boolean whether to continue collecting articles
|
||||
// i.e. date is after oldest cutoff
|
||||
private function collectArticle($url) {
|
||||
if (array_key_exists($url, $this->seen)) {
|
||||
return true;
|
||||
}
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
|
||||
$rssItem = array( 'uri' => $url, 'uid' => $url );
|
||||
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
|
||||
$dt = $html->find('meta[property=article:published_time]', 0)->content;
|
||||
// Exit if too old
|
||||
if (strtotime($dt) < $this->oldest) {
|
||||
return false;
|
||||
}
|
||||
$rssItem['timestamp'] = $dt;
|
||||
$img = $html->find('meta[property=og:image]', 0);
|
||||
$rssItem['enclosures'] = $img ? array($img->content) : array();
|
||||
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
|
||||
$rssItem['categories'] = array_map(function ($link) {
|
||||
return trim($link->plaintext);
|
||||
}, $html->find('.b-single-header__categories .c-category-list a'));
|
||||
$rssItem['content'] = trim($html->find('article', 0)->innertext);
|
||||
|
||||
$this->items[] = $rssItem;
|
||||
$this->seen[$url] = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -30,7 +30,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'defaultValue' => false,
|
||||
'title' => 'Feed includes reviews when checked'
|
||||
'title' => 'Feed includes reviews when unchecked'
|
||||
)
|
||||
),
|
||||
'Group' => array(
|
||||
@@ -175,7 +175,13 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$header = array();
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
$touchURI = str_replace(
|
||||
'https://www.facebook',
|
||||
'https://touch.facebook',
|
||||
$this->getURI()
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($touchURI, $header)
|
||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||
|
||||
if(!$this->isPublicGroup($html)) {
|
||||
@@ -186,19 +192,18 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$this->groupName = $this->extractGroupName($html);
|
||||
|
||||
$posts = $html->find('div.userContentWrapper')
|
||||
$posts = $html->find('div.story_body_container')
|
||||
or returnServerError('Failed finding posts!');
|
||||
|
||||
foreach($posts as $post) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$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);
|
||||
$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);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
@@ -215,16 +220,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$urlparts = parse_url($group);
|
||||
|
||||
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']
|
||||
. '"!');
|
||||
|
||||
}
|
||||
$this->validateHost($urlparts['host']);
|
||||
|
||||
return explode('/', $urlparts['path'])[2];
|
||||
|
||||
@@ -236,24 +232,47 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
private function validateHost($provided_host) {
|
||||
// Handle mobile links
|
||||
if (strpos($provided_host, 'm.') === 0) {
|
||||
$provided_host = substr($provided_host, strlen('m.'));
|
||||
}
|
||||
if (strpos($provided_host, 'touch.') === 0) {
|
||||
$provided_host = substr($provided_host, strlen('touch.'));
|
||||
}
|
||||
|
||||
$facebook_host = parse_url(self::URI)['host'];
|
||||
|
||||
if ($provided_host !== $facebook_host
|
||||
&& 'www.' . $provided_host !== $facebook_host) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $provided_host
|
||||
. '", expected "'
|
||||
. $facebook_host
|
||||
. '"!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $html simple_html_dom
|
||||
* @return bool
|
||||
*/
|
||||
private function isPublicGroup($html) {
|
||||
|
||||
// Facebook redirects to the groups about page for non-public groups
|
||||
$about = $html->find('#pagelet_group_about', 0);
|
||||
|
||||
return !($about);
|
||||
|
||||
// Facebook touch just presents a login page for non-public groups
|
||||
$title = $html->find('title', 0);
|
||||
return $title->plaintext !== 'Log in to Facebook | Facebook';
|
||||
}
|
||||
|
||||
private function extractGroupName($html) {
|
||||
|
||||
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
||||
$ogtitle = $html->find('._de1', 0)
|
||||
or returnServerError('Unable to find group title!');
|
||||
|
||||
return html_entity_decode($ogtitle->content, ENT_QUOTES);
|
||||
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES);
|
||||
}
|
||||
|
||||
private function extractGroupURI($post) {
|
||||
private function extractGroupPostURI($post) {
|
||||
|
||||
$elements = $post->find('a')
|
||||
or returnServerError('Unable to find URI!');
|
||||
@@ -262,7 +281,8 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
// Find the one that is a permalink
|
||||
if(strpos($anchor->href, 'permalink') !== false) {
|
||||
return $anchor->href;
|
||||
$arr = explode('?', $anchor->href, 2);
|
||||
return $arr[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -271,57 +291,61 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupContent($post) {
|
||||
private function extractGroupPostContent($post) {
|
||||
|
||||
$content = $post->find('div.userContent', 0)
|
||||
$content = $post->find('div._5rgt', 0)
|
||||
or returnServerError('Unable to find user content!');
|
||||
|
||||
return $content->innertext . $content->next_sibling()->innertext;
|
||||
$context_text = $content->innertext;
|
||||
if ($content->next_sibling() !== null) {
|
||||
$context_text .= $content->next_sibling()->innertext;
|
||||
}
|
||||
return $context_text;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTimestamp($post) {
|
||||
private function extractGroupPostAuthor($post) {
|
||||
|
||||
$element = $post->find('abbr[data-utime]', 0)
|
||||
or returnServerError('Unable to find timestamp!');
|
||||
|
||||
return $element->getAttribute('data-utime');
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupAuthor($post) {
|
||||
|
||||
$element = $post->find('img', 0)
|
||||
$element = $post->find('h3 a', 0)
|
||||
or returnServerError('Unable to find author information!');
|
||||
|
||||
return $element->{'aria-label'};
|
||||
return $element->plaintext;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupEnclosures($post) {
|
||||
private function extractGroupPostEnclosures($post) {
|
||||
|
||||
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
|
||||
$elements = $post->find('span._6qdm');
|
||||
if ($post->find('div._5rgt', 0)->next_sibling() !== null) {
|
||||
array_push($elements, ...$post->find('div._5rgt', 0)->next_sibling()->find('i.img'));
|
||||
}
|
||||
|
||||
$enclosures = array();
|
||||
|
||||
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
|
||||
|
||||
foreach($elements as $enclosure) {
|
||||
$enclosures[] = $enclosure->src;
|
||||
if(preg_match($background_img_regex, $enclosure, $matches) > 0) {
|
||||
$bg_img_value = trim(html_entity_decode($matches[1], ENT_QUOTES), "'\"");
|
||||
$bg_img_url = urldecode(preg_replace('/\\\([0-9a-z]{2}) /', '%$1', $bg_img_value));
|
||||
$enclosures[] = urldecode($bg_img_url);
|
||||
}
|
||||
}
|
||||
|
||||
return empty($enclosures) ? null : $enclosures;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTitle($post) {
|
||||
private function extractGroupPostTitle($post) {
|
||||
|
||||
$element = $post->find('h5', 0)
|
||||
$element = $post->find('h3', 0)
|
||||
or returnServerError('Unable to find title!');
|
||||
|
||||
if(strpos($element->plaintext, 'shared') === false) {
|
||||
|
||||
$content = strip_tags($this->extractGroupContent($post));
|
||||
$content = strip_tags($this->extractGroupPostContent($post));
|
||||
|
||||
return $this->extractGroupAuthor($post)
|
||||
return $this->extractGroupPostAuthor($post)
|
||||
. ' posted: '
|
||||
. substr(
|
||||
$content,
|
||||
@@ -348,13 +372,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$urlparts = parse_url($user);
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
}
|
||||
$this->validateHost($urlparts['host']);
|
||||
|
||||
if(!array_key_exists('path', $urlparts)
|
||||
|| $urlparts['path'] === '/') {
|
||||
@@ -555,7 +573,7 @@ EOD;
|
||||
}
|
||||
|
||||
// No captcha? We can carry on retrieving page contents :)
|
||||
// First, we check wether the page is public or not
|
||||
// First, we check whether the page is public or not
|
||||
$loginForm = $html->find('._585r', 0);
|
||||
|
||||
if($loginForm != null) {
|
||||
|
@@ -35,6 +35,8 @@ class FicbookBridge extends BridgeAbstract {
|
||||
),
|
||||
);
|
||||
|
||||
protected $titleName;
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Site News': {
|
||||
@@ -56,6 +58,21 @@ class FicbookBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Site News': {
|
||||
return $this->queriedContext . ' | ' . self::NAME;
|
||||
}
|
||||
case 'Fiction Updates': {
|
||||
return $this->titleName . ' | ' . self::NAME;
|
||||
}
|
||||
case 'Fiction Comments': {
|
||||
return $this->titleName . ' | Comments | ' . self::NAME;
|
||||
}
|
||||
default: return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$header = array('Accept-Language: en-US');
|
||||
@@ -65,6 +82,10 @@ class FicbookBridge extends BridgeAbstract {
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
if ($this->queriedContext == 'Fiction Updates' or $this->queriedContext == 'Fiction Comments') {
|
||||
$this->titleName = $html->find('.fanfic-main-info > h1', 0)->innertext;
|
||||
}
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Site News': return $this->collectSiteNews($html);
|
||||
case 'Fiction Updates': return $this->collectUpdatesData($html);
|
||||
@@ -84,7 +105,7 @@ class FicbookBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function collectCommentsData($html) {
|
||||
foreach($html->find('article.post') as $article) {
|
||||
foreach($html->find('article.comment-container') as $article) {
|
||||
$this->items[] = array(
|
||||
'uri' => $article->find('.comment_link_to_fic > a', 0)->href,
|
||||
'title' => $article->find('.comment_author', 0)->plaintext,
|
||||
@@ -97,7 +118,7 @@ class FicbookBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function collectUpdatesData($html) {
|
||||
foreach($html->find('ul.table-of-contents > li') as $chapter) {
|
||||
foreach($html->find('ul.list-of-fanfic-parts > li') as $chapter) {
|
||||
$item = array(
|
||||
'uri' => $chapter->find('a', 0)->href,
|
||||
'title' => $chapter->find('a', 0)->plaintext,
|
||||
@@ -130,10 +151,10 @@ class FicbookBridge extends BridgeAbstract {
|
||||
'июня',
|
||||
'июля',
|
||||
'августа',
|
||||
'Сентября',
|
||||
'сентября',
|
||||
'октября',
|
||||
'Ноября',
|
||||
'Декабря',
|
||||
'ноября',
|
||||
'декабря',
|
||||
);
|
||||
|
||||
$en_month = array(
|
||||
|
104
bridges/FirefoxAddonsBridge.php
Normal file
104
bridges/FirefoxAddonsBridge.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
class FirefoxAddonsBridge extends BridgeAbstract {
|
||||
const NAME = 'Firefox Add-ons Bridge';
|
||||
const URI = 'https://addons.mozilla.org/';
|
||||
const DESCRIPTION = 'Returns version history for a Firefox Add-on.';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'id' => array(
|
||||
'name' => 'Add-on ID',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'save-to-the-wayback-machine',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
private $feedName = '';
|
||||
private $releaseDateRegex = '/Released ([\w, ]+) - ([\w. ]+)/';
|
||||
private $xpiFileRegex = '/([A-Za-z0-9_.-]+)\.xpi$/';
|
||||
private $outgoingRegex = '/https:\/\/outgoing.prod.mozaws.net\/v1\/(?:[A-z0-9]+)\//';
|
||||
|
||||
private $urlRegex = '/addons\.mozilla\.org\/(?:[\w-]+\/)?firefox\/addon\/([\w-]+)/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
if(preg_match($this->urlRegex, $url, $matches)) {
|
||||
$params['id'] = $matches[1];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$this->feedName = $html->find('h1[class="AddonTitle"] > a', 0)->innertext;
|
||||
$author = $html->find('span.AddonTitle-author > a', 0)->plaintext;
|
||||
|
||||
foreach ($html->find('div.AddonVersionCard-content') as $div) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $div->find('h2.AddonVersionCard-version', 0)->plaintext;
|
||||
$item['uid'] = $item['title'];
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['author'] = $author;
|
||||
|
||||
if (preg_match($this->releaseDateRegex, $div->find('div.AddonVersionCard-fileInfo', 0)->plaintext, $match)) {
|
||||
$item['timestamp'] = $match[1];
|
||||
$size = $match[2];
|
||||
}
|
||||
|
||||
$compatibility = $div->find('div.AddonVersionCard-compatibility', 0)->plaintext;
|
||||
$license = $div->find('p.AddonVersionCard-license', 0)->innertext;
|
||||
$downloadlink = $div->find('a.InstallButtonWrapper-download-link', 0)->href;
|
||||
$releaseNotes = $this->removeOutgoinglink($div->find('div.AddonVersionCard-releaseNotes', 0));
|
||||
|
||||
if (preg_match($this->xpiFileRegex, $downloadlink, $match)) {
|
||||
$xpiFilename = $match[0];
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<strong>Release Notes</strong>
|
||||
<p>{$releaseNotes}</p>
|
||||
<strong>Compatibility</strong>
|
||||
<p>{$compatibility}</p>
|
||||
<strong>License</strong>
|
||||
<p>{$license}</p>
|
||||
<strong>Download</strong>
|
||||
<p><a href="{$downloadlink}">{$xpiFilename}</a> ($size)</p>
|
||||
EOD;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('id'))) {
|
||||
return self::URI . 'en-US/firefox/addon/' . $this->getInput('id') . '/versions/';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName . ' - Firefox Add-on';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function removeOutgoinglink($html) {
|
||||
foreach ($html->find('a') as $a) {
|
||||
$a->href = urldecode(preg_replace($this->outgoingRegex, '', $a->href));
|
||||
}
|
||||
|
||||
return $html->innertext;
|
||||
}
|
||||
}
|
@@ -20,6 +20,27 @@ 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(
|
||||
@@ -29,30 +50,60 @@ 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',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
private $username = '';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Explore':
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'explore')
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Flickr.');
|
||||
break;
|
||||
|
||||
case 'By keyword':
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
break;
|
||||
|
||||
case 'By username':
|
||||
$filter = 'photo-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
|
||||
//$filter = 'photo-models';
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
|
||||
$this->username = $this->getInput('u');
|
||||
|
||||
if ($html->find('span.search-pill-name', 0)) {
|
||||
$this->username = $html->find('span.search-pill-name', 0)->plaintext;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -64,7 +115,6 @@ 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
|
||||
@@ -72,12 +122,12 @@ class FlickrBridge extends BridgeAbstract {
|
||||
* the author is part of the owner data.
|
||||
*/
|
||||
if(array_key_exists('username', $model)) {
|
||||
$item['author'] = $model['username'];
|
||||
$item['author'] = urldecode($model['username']);
|
||||
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
||||
$item['author'] = reset($model_json)[0]['owner']['username'];
|
||||
$item['author'] = urldecode(reset($model_json)[0]['owner']['username']);
|
||||
}
|
||||
|
||||
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
|
||||
$item['title'] = urldecode((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'] : '');
|
||||
@@ -87,7 +137,7 @@ class FlickrBridge extends BridgeAbstract {
|
||||
. '"><img src="'
|
||||
. $this->extractContentImage($model)
|
||||
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
|
||||
. $description
|
||||
. urldecode($description)
|
||||
. '</p>';
|
||||
|
||||
$item['enclosures'] = $this->extractEnclosures($model);
|
||||
@@ -98,6 +148,46 @@ 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
|
||||
|
@@ -26,8 +26,9 @@ class FolhaDeSaoPauloBridge extends FeedExpander {
|
||||
$item_content = $articleHTMLContent->find('div.c-news__body', 0);
|
||||
if ($item_content) {
|
||||
$text = $item_content->innertext;
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><img><em>');
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><figure><figcaption><img><strong><em>');
|
||||
$item['content'] = $text;
|
||||
$item['uri'] = explode('*', $item['uri'])[1];
|
||||
}
|
||||
} else {
|
||||
Debug::log('???: ' . $item['uri']);
|
||||
|
@@ -96,7 +96,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$contents = $article->find('section.article-text-classic', 0)->innertext;
|
||||
$contents = $article->find('section.article-text', 1)->innertext;
|
||||
$headline = trim($article->find('p.description', 0)->plaintext);
|
||||
if(!empty($headline))
|
||||
$headline = '<p><b>' . $headline . '</b></p>';
|
||||
@@ -129,6 +129,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = StripWithDelimiters($contents, '<section class="module-toretain module-propal-nl', '</section>');
|
||||
$contents = stripWithDelimiters($contents, '<script ', '</script>');
|
||||
$contents = stripWithDelimiters($contents, '<script>', '</script>');
|
||||
|
||||
return $headline . trim($contents);
|
||||
}
|
||||
|
@@ -113,8 +113,8 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
break;
|
||||
case 'T':
|
||||
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
|
||||
$url = self::URI . $tutorialItem->find('a', 0)->href;
|
||||
$title = $tutorialItem->find('a', 0)->plaintext;
|
||||
$url = self::URI . $tutorialItem->find('a', 1)->href;
|
||||
$title = $tutorialItem->find('a', 1)->plaintext;
|
||||
$time = $this->findItemDate($tutorialItem);
|
||||
$author = $tutorialItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
|
71
bridges/GenshinImpactBridge.php
Normal file
71
bridges/GenshinImpactBridge.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
class GenshinImpactBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'corenting';
|
||||
const NAME = 'Genshin Impact';
|
||||
const URI = 'https://genshin.mihoyo.com/en/news';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'News from the Genshin Impact website';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Latest' => 10,
|
||||
'Info' => 11,
|
||||
'Updates' => 12,
|
||||
'Events' => 13
|
||||
),
|
||||
'defaultValue' => 10
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$category = $this->getInput('category');
|
||||
|
||||
$url = 'https://genshin.mihoyo.com/content/yuanshen/getContentList';
|
||||
$url = $url . '?pageSize=3&pageNum=1&channelId=' . $category;
|
||||
$api_response = getContents($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
$json_list = json_decode($api_response, true);
|
||||
|
||||
foreach($json_list['data']['list'] as $json_item) {
|
||||
$article_url = 'https://genshin.mihoyo.com/content/yuanshen/getContent';
|
||||
$article_url = $article_url . '?contentId=' . $json_item['contentId'];
|
||||
$article_res = getContents($article_url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
$article_json = json_decode($article_res, true);
|
||||
$article_time = $article_json['data']['start_time'];
|
||||
$timezone = 'Asia/Shanghai';
|
||||
$article_timestamp = new DateTime($article_time, new DateTimeZone($timezone));
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $article_json['data']['title'];
|
||||
$item['timestamp'] = $article_timestamp->format('U');
|
||||
$item['content'] = $article_json['data']['content'];
|
||||
$item['uri'] = $this->getArticleUri($json_item);
|
||||
$item['id'] = $json_item['contentId'];
|
||||
|
||||
// Picture
|
||||
foreach($article_json['data']['ext'] as $ext) {
|
||||
if ($ext['arrtName'] == 'banner' && count($ext['value']) == 1) {
|
||||
$item['enclosures'] = array($ext['value'][0]['url']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://genshin.mihoyo.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function getArticleUri($json_item) {
|
||||
return 'https://genshin.mihoyo.com/en/news/detail/' . $json_item['contentId'];
|
||||
}
|
||||
}
|
@@ -33,17 +33,23 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
// Allows generalization with GithubPullRequestBridge
|
||||
const BRIDGE_OPTIONS = array(0 => 'Project Issues', 1 => 'Issue comments');
|
||||
const URL_PATH = 'issues';
|
||||
const SEARCH_QUERY_PATH = 'issues';
|
||||
const SEARCH_QUERY = '?q=is%3Aissue+sort%3Aupdated-desc';
|
||||
|
||||
public function getName(){
|
||||
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
||||
switch($this->queriedContext) {
|
||||
case 'Project Issues':
|
||||
case static::BRIDGE_OPTIONS[0]: // Project Issues
|
||||
$prefix = static::NAME . 's for ';
|
||||
if($this->getInput('c')) {
|
||||
$prefix = static::NAME . 's comments for ';
|
||||
}
|
||||
$name = $prefix . $name;
|
||||
break;
|
||||
case 'Issue comments':
|
||||
case static::BRIDGE_OPTIONS[1]: // Issue comments
|
||||
$name = static::NAME . ' ' . $name . ' #' . $this->getInput('i');
|
||||
break;
|
||||
default: return parent::getName();
|
||||
@@ -51,14 +57,14 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
public function getURI() {
|
||||
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues';
|
||||
if($this->queriedContext === 'Issue comments') {
|
||||
$uri .= '/' . $this->getInput('i');
|
||||
} elseif($this->getInput('c')) {
|
||||
$uri .= '?q=is%3Aissue+sort%3Aupdated-desc';
|
||||
. $this->getInput('p') . '/';
|
||||
if($this->queriedContext === static::BRIDGE_OPTIONS[1]) {
|
||||
$uri .= static::URL_PATH . '/' . $this->getInput('i');
|
||||
} else {
|
||||
$uri .= static::SEARCH_QUERY_PATH . static::SEARCH_QUERY;
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
@@ -72,19 +78,19 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
. $this->getInput('u')
|
||||
. '/'
|
||||
. $this->getInput('p')
|
||||
. '/issues/'
|
||||
. '/' . static::URL_PATH . '/'
|
||||
. $issue_number
|
||||
. '#'
|
||||
. $comment_id;
|
||||
}
|
||||
|
||||
private function extractIssueEvent($issueNbr, $title, $comment){
|
||||
private function extractIssueEvent($issueNbr, $title, $comment) {
|
||||
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
|
||||
|
||||
$author = $comment->find('.author', 0);
|
||||
$author = $comment->find('.author, .avatar', 0);
|
||||
if ($author) {
|
||||
$author = $author->plaintext;
|
||||
$author = trim($author->href, '/');
|
||||
} else {
|
||||
$author = '';
|
||||
}
|
||||
@@ -95,22 +101,27 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$comment->find('.octicon', 0)->getAttribute('class')
|
||||
));
|
||||
|
||||
$time = $comment->find('relative-time', 0);
|
||||
if ($time === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($comment->find('.Details-content--hidden, .btn') as $el) {
|
||||
$el->innertext = '';
|
||||
}
|
||||
$content = $comment->plaintext;
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractIssueComment($issueNbr, $title, $comment){
|
||||
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->parent->id);
|
||||
private function extractIssueComment($issueNbr, $title, $comment) {
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
@@ -118,20 +129,23 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$comment->find('.timeline-comment-header-text', 0)->plaintext
|
||||
);
|
||||
|
||||
$time = $comment->find('relative-time', 0);
|
||||
if ($time === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $comment->find('.comment-body', 0)->innertext;
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractIssueComments($issue){
|
||||
private function extractIssueComments($issue) {
|
||||
$items = array();
|
||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
||||
$issueNbr = trim(
|
||||
@@ -146,41 +160,45 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
if ($comment->hasClass('comment')) {
|
||||
$comment = $comment->parent;
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
if ($item !== null) {
|
||||
$items[] = $item;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
$comment = $comment->parent;
|
||||
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
if ($item !== null) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError(
|
||||
'No results for Github Issue ' . $this->getURI()
|
||||
'No results for ' . static::NAME . ' ' . $this->getURI()
|
||||
);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Issue comments':
|
||||
case static::BRIDGE_OPTIONS[1]: // Issue comments
|
||||
$this->items = $this->extractIssueComments($html);
|
||||
break;
|
||||
case 'Project Issues':
|
||||
case static::BRIDGE_OPTIONS[0]: // Project Issues
|
||||
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
||||
$info = $issue->find('.opened-by', 0);
|
||||
$issueNbr = substr(
|
||||
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
|
||||
);
|
||||
|
||||
preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match);
|
||||
$issueNbr = $match[1];
|
||||
|
||||
$item = array();
|
||||
$item['content'] = '';
|
||||
|
||||
if($this->getInput('c')) {
|
||||
$uri = static::URI . $this->getInput('u')
|
||||
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
. '/' . $this->getInput('p') . '/' . static::URL_PATH . '/' . $issueNbr;
|
||||
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
|
||||
if($issue) {
|
||||
$this->items = array_merge(
|
||||
@@ -209,7 +227,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
$item['content'] .= "\n" . 'Comments: ' . $comment_count;
|
||||
$item['uri'] = self::URI
|
||||
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
. trim($issue->find('.js-navigation-open', 0)->getAttribute('href'), '/');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
@@ -247,7 +265,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$show_comments = 'off';
|
||||
} break;
|
||||
case 3: { // Project issues with issue comments
|
||||
if($path_segments[2] !== 'issues') {
|
||||
if($path_segments[2] !== static::URL_PATH) {
|
||||
return null;
|
||||
}
|
||||
list($user, $project) = $path_segments;
|
||||
|
38
bridges/GithubPullRequestBridge.php
Normal file
38
bridges/GithubPullRequestBridge.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
require_once('GithubIssueBridge.php');
|
||||
class GitHubPullRequestBridge extends GithubIssueBridge {
|
||||
const MAINTAINER = 'Yaman Qalieh';
|
||||
const NAME = 'GitHub Pull Request';
|
||||
const DESCRIPTION = 'Returns the pull request or comments of a pull request of a GitHub project';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'u' => array(
|
||||
'name' => 'User name',
|
||||
'required' => true
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Project name',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'Project Pull Requests' => array(
|
||||
'c' => array(
|
||||
'name' => 'Show Pull Request Comments',
|
||||
'type' => 'checkbox'
|
||||
)
|
||||
),
|
||||
'Pull Request comments' => array(
|
||||
'i' => array(
|
||||
'name' => 'Pull Request number',
|
||||
'type' => 'number',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const BRIDGE_OPTIONS = array(0 => 'Project Pull Requests', 1 => 'Pull Request comments');
|
||||
const URL_PATH = 'pull';
|
||||
const SEARCH_QUERY_PATH = 'pulls';
|
||||
const SEARCH_QUERY = '?q=is%3Apr+sort%3Aupdated-desc';
|
||||
}
|
636
bridges/GithubTrendingBridge.php
Normal file
636
bridges/GithubTrendingBridge.php
Normal file
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
class GithubTrendingBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'liamka';
|
||||
const NAME = 'Github Trending';
|
||||
const URI = 'https://github.com/trending';
|
||||
const URI_ITEM = 'https://github.com';
|
||||
const CACHE_TIMEOUT = 43200; // 12hr
|
||||
const DESCRIPTION = 'See what the GitHub community is most excited repos.';
|
||||
const PARAMETERS = array(
|
||||
'By language' => array(
|
||||
'language' => array(
|
||||
'name' => 'Select language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All languages' => '',
|
||||
'C++' => 'c++',
|
||||
'HTML' => 'html',
|
||||
'Java' => 'java',
|
||||
'JavaScript' => 'javascript',
|
||||
'PHP' => 'php',
|
||||
'Python' => 'python',
|
||||
'Ruby' => 'ruby',
|
||||
'Unknown languages' => 'unknown languages',
|
||||
'1C Enterprise' => '1c enterprise',
|
||||
'4D' => '4d',
|
||||
'ABAP' => 'abap',
|
||||
'ABNF' => 'abnf',
|
||||
'ActionScript' => 'actionscript',
|
||||
'Ada' => 'ada',
|
||||
'Adobe Font Metrics' => 'adobe font metrics',
|
||||
'Agda' => 'agda',
|
||||
'AGS Script' => 'ags script',
|
||||
'Alloy' => 'alloy',
|
||||
'Alpine Abuild' => 'alpine abuild',
|
||||
'Altium Designer' => 'altium designer',
|
||||
'AMPL' => 'ampl',
|
||||
'AngelScript' => 'angelscript',
|
||||
'Ant Build System' => 'ant build system',
|
||||
'ANTLR' => 'antlr',
|
||||
'ApacheConf' => 'apacheconf',
|
||||
'Apex' => 'apex',
|
||||
'API Blueprint' => 'api blueprint',
|
||||
'APL' => 'apl',
|
||||
'Apollo Guidance Computer' => 'apollo guidance computer',
|
||||
'AppleScript' => 'applescript',
|
||||
'Arc' => 'arc',
|
||||
'AsciiDoc' => 'asciidoc',
|
||||
'ASN.1' => 'asn.1',
|
||||
'ASP' => 'asp',
|
||||
'AspectJ' => 'aspectj',
|
||||
'Assembly' => 'assembly',
|
||||
'Asymptote' => 'asymptote',
|
||||
'ATS' => 'ats',
|
||||
'Augeas' => 'augeas',
|
||||
'AutoHotkey' => 'autohotkey',
|
||||
'AutoIt' => 'autoit',
|
||||
'Awk' => 'awk',
|
||||
'Ballerina' => 'ballerina',
|
||||
'Batchfile' => 'batchfile',
|
||||
'Befunge' => 'befunge',
|
||||
'BibTeX' => 'bibtex',
|
||||
'Bison' => 'bison',
|
||||
'BitBake' => 'bitbake',
|
||||
'Blade' => 'blade',
|
||||
'BlitzBasic' => 'blitzbasic',
|
||||
'BlitzMax' => 'blitzmax',
|
||||
'Bluespec' => 'bluespec',
|
||||
'Boo' => 'boo',
|
||||
'Brainfuck' => 'brainfuck',
|
||||
'Brightscript' => 'brightscript',
|
||||
'Zeek' => 'zeek',
|
||||
'C' => 'c',
|
||||
'C#' => 'c#',
|
||||
'C++' => 'c++',
|
||||
'C-ObjDump' => 'c-objdump',
|
||||
'C2hs Haskell' => 'c2hs haskell',
|
||||
'Cabal Config' => 'cabal config',
|
||||
'CartoCSS' => 'cartocss',
|
||||
'Ceylon' => 'ceylon',
|
||||
'Chapel' => 'chapel',
|
||||
'Charity' => 'charity',
|
||||
'ChucK' => 'chuck',
|
||||
'Cirru' => 'cirru',
|
||||
'Clarion' => 'clarion',
|
||||
'Clean' => 'clean',
|
||||
'Click' => 'click',
|
||||
'CLIPS' => 'clips',
|
||||
'Clojure' => 'clojure',
|
||||
'Closure Templates' => 'closure templates',
|
||||
'Cloud Firestore Security Rules' => 'cloud firestore security rules',
|
||||
'CMake' => 'cmake',
|
||||
'COBOL' => 'cobol',
|
||||
'CodeQL' => 'codeql',
|
||||
'CoffeeScript' => 'coffeescript',
|
||||
'ColdFusion' => 'coldfusion',
|
||||
'ColdFusion CFC' => 'coldfusion cfc',
|
||||
'COLLADA' => 'collada',
|
||||
'Common Lisp' => 'common lisp',
|
||||
'Common Workflow Language' => 'common workflow language',
|
||||
'Component Pascal' => 'component pascal',
|
||||
'CoNLL-U' => 'conll-u',
|
||||
'Cool' => 'cool',
|
||||
'Coq' => 'coq',
|
||||
'Cpp-ObjDump' => 'cpp-objdump',
|
||||
'Creole' => 'creole',
|
||||
'Crystal' => 'crystal',
|
||||
'CSON' => 'cson',
|
||||
'Csound' => 'csound',
|
||||
'Csound Document' => 'csound document',
|
||||
'Csound Score' => 'csound score',
|
||||
'CSS' => 'css',
|
||||
'CSV' => 'csv',
|
||||
'Cuda' => 'cuda',
|
||||
'cURL Config' => 'curl config',
|
||||
'CWeb' => 'cweb',
|
||||
'Cycript' => 'cycript',
|
||||
'Cython' => 'cython',
|
||||
'D' => 'd',
|
||||
'D-ObjDump' => 'd-objdump',
|
||||
'Darcs Patch' => 'darcs patch',
|
||||
'Dart' => 'dart',
|
||||
'DataWeave' => 'dataweave',
|
||||
'desktop' => 'desktop',
|
||||
'Dhall' => 'dhall',
|
||||
'Diff' => 'diff',
|
||||
'DIGITAL Command Language' => 'digital command language',
|
||||
'dircolors' => 'dircolors',
|
||||
'DirectX 3D File' => 'directx 3d file',
|
||||
'DM' => 'dm',
|
||||
'DNS Zone' => 'dns zone',
|
||||
'Dockerfile' => 'dockerfile',
|
||||
'Dogescript' => 'dogescript',
|
||||
'DTrace' => 'dtrace',
|
||||
'Dylan' => 'dylan',
|
||||
'E' => 'e',
|
||||
'Eagle' => 'eagle',
|
||||
'Easybuild' => 'easybuild',
|
||||
'EBNF' => 'ebnf',
|
||||
'eC' => 'ec',
|
||||
'Ecere Projects' => 'ecere projects',
|
||||
'ECL' => 'ecl',
|
||||
'ECLiPSe' => 'eclipse',
|
||||
'EditorConfig' => 'editorconfig',
|
||||
'Edje Data Collection' => 'edje data collection',
|
||||
'edn' => 'edn',
|
||||
'Eiffel' => 'eiffel',
|
||||
'EJS' => 'ejs',
|
||||
'Elixir' => 'elixir',
|
||||
'Elm' => 'elm',
|
||||
'Emacs Lisp' => 'emacs lisp',
|
||||
'EmberScript' => 'emberscript',
|
||||
'EML' => 'eml',
|
||||
'EQ' => 'eq',
|
||||
'Erlang' => 'erlang',
|
||||
'F#' => 'f#',
|
||||
'F*' => 'f*',
|
||||
'Factor' => 'factor',
|
||||
'Fancy' => 'fancy',
|
||||
'Fantom' => 'fantom',
|
||||
'Faust' => 'faust',
|
||||
'FIGlet Font' => 'figlet font',
|
||||
'Filebench WML' => 'filebench wml',
|
||||
'Filterscript' => 'filterscript',
|
||||
'fish' => 'fish',
|
||||
'FLUX' => 'flux',
|
||||
'Formatted' => 'formatted',
|
||||
'Forth' => 'forth',
|
||||
'Fortran' => 'fortran',
|
||||
'FreeMarker' => 'freemarker',
|
||||
'Frege' => 'frege',
|
||||
'G-code' => 'g-code',
|
||||
'Game Maker Language' => 'game maker language',
|
||||
'GAML' => 'gaml',
|
||||
'GAMS' => 'gams',
|
||||
'GAP' => 'gap',
|
||||
'GCC Machine Description' => 'gcc machine description',
|
||||
'GDB' => 'gdb',
|
||||
'GDScript' => 'gdscript',
|
||||
'Genie' => 'genie',
|
||||
'Genshi' => 'genshi',
|
||||
'Gentoo Ebuild' => 'gentoo ebuild',
|
||||
'Gentoo Eclass' => 'gentoo eclass',
|
||||
'Gerber Image' => 'gerber image',
|
||||
'Gettext Catalog' => 'gettext catalog',
|
||||
'Gherkin' => 'gherkin',
|
||||
'Git Attributes' => 'git attributes',
|
||||
'Git Config' => 'git config',
|
||||
'GLSL' => 'glsl',
|
||||
'Glyph' => 'glyph',
|
||||
'Glyph Bitmap Distribution Format' => 'glyph bitmap distribution format',
|
||||
'GN' => 'gn',
|
||||
'Gnuplot' => 'gnuplot',
|
||||
'Go' => 'go',
|
||||
'Golo' => 'golo',
|
||||
'Gosu' => 'gosu',
|
||||
'Grace' => 'grace',
|
||||
'Gradle' => 'gradle',
|
||||
'Grammatical Framework' => 'grammatical framework',
|
||||
'Graph Modeling Language' => 'graph modeling language',
|
||||
'GraphQL' => 'graphql',
|
||||
'Graphviz (DOT)' => 'graphviz (dot)',
|
||||
'Groovy' => 'groovy',
|
||||
'Groovy Server Pages' => 'groovy server pages',
|
||||
'Hack' => 'hack',
|
||||
'Haml' => 'haml',
|
||||
'Handlebars' => 'handlebars',
|
||||
'HAProxy' => 'haproxy',
|
||||
'Harbour' => 'harbour',
|
||||
'Haskell' => 'haskell',
|
||||
'Haxe' => 'haxe',
|
||||
'HCL' => 'hcl',
|
||||
'HiveQL' => 'hiveql',
|
||||
'HLSL' => 'hlsl',
|
||||
'HolyC' => 'holyc',
|
||||
'HTML' => 'html',
|
||||
'HTML+Django' => 'html+django',
|
||||
'HTML+ECR' => 'html+ecr',
|
||||
'HTML+EEX' => 'html+eex',
|
||||
'HTML+ERB' => 'html+erb',
|
||||
'HTML+PHP' => 'html+php',
|
||||
'HTML+Razor' => 'html+razor',
|
||||
'HTTP' => 'http',
|
||||
'HXML' => 'hxml',
|
||||
'Hy' => 'hy',
|
||||
'HyPhy' => 'hyphy',
|
||||
'IDL' => 'idl',
|
||||
'Idris' => 'idris',
|
||||
'Ignore List' => 'ignore list',
|
||||
'IGOR Pro' => 'igor pro',
|
||||
'Inform 7' => 'inform 7',
|
||||
'INI' => 'ini',
|
||||
'Inno Setup' => 'inno setup',
|
||||
'Io' => 'io',
|
||||
'Ioke' => 'ioke',
|
||||
'IRC log' => 'irc log',
|
||||
'Isabelle' => 'isabelle',
|
||||
'Isabelle ROOT' => 'isabelle root',
|
||||
'J' => 'j',
|
||||
'Jasmin' => 'jasmin',
|
||||
'Java' => 'java',
|
||||
'Java Properties' => 'java properties',
|
||||
'Java Server Pages' => 'java server pages',
|
||||
'JavaScript' => 'javascript',
|
||||
'JavaScript+ERB' => 'javascript+erb',
|
||||
'JFlex' => 'jflex',
|
||||
'Jison' => 'jison',
|
||||
'Jison Lex' => 'jison lex',
|
||||
'Jolie' => 'jolie',
|
||||
'JSON' => 'json',
|
||||
'JSON with Comments' => 'json with comments',
|
||||
'JSON5' => 'json5',
|
||||
'JSONiq' => 'jsoniq',
|
||||
'JSONLD' => 'jsonld',
|
||||
'Jsonnet' => 'jsonnet',
|
||||
'JSX' => 'jsx',
|
||||
'Julia' => 'julia',
|
||||
'Jupyter Notebook' => 'jupyter notebook',
|
||||
'KiCad Layout' => 'kicad layout',
|
||||
'KiCad Legacy Layout' => 'kicad legacy layout',
|
||||
'KiCad Schematic' => 'kicad schematic',
|
||||
'Kit' => 'kit',
|
||||
'Kotlin' => 'kotlin',
|
||||
'KRL' => 'krl',
|
||||
'LabVIEW' => 'labview',
|
||||
'Lasso' => 'lasso',
|
||||
'Latte' => 'latte',
|
||||
'Lean' => 'lean',
|
||||
'Less' => 'less',
|
||||
'Lex' => 'lex',
|
||||
'LFE' => 'lfe',
|
||||
'LilyPond' => 'lilypond',
|
||||
'Limbo' => 'limbo',
|
||||
'Linker Script' => 'linker script',
|
||||
'Linux Kernel Module' => 'linux kernel module',
|
||||
'Liquid' => 'liquid',
|
||||
'Literate Agda' => 'literate agda',
|
||||
'Literate CoffeeScript' => 'literate coffeescript',
|
||||
'Literate Haskell' => 'literate haskell',
|
||||
'LiveScript' => 'livescript',
|
||||
'LLVM' => 'llvm',
|
||||
'Logos' => 'logos',
|
||||
'Logtalk' => 'logtalk',
|
||||
'LOLCODE' => 'lolcode',
|
||||
'LookML' => 'lookml',
|
||||
'LoomScript' => 'loomscript',
|
||||
'LSL' => 'lsl',
|
||||
'LTspice Symbol' => 'ltspice symbol',
|
||||
'Lua' => 'lua',
|
||||
'M' => 'm',
|
||||
'M4' => 'm4',
|
||||
'M4Sugar' => 'm4sugar',
|
||||
'Makefile' => 'makefile',
|
||||
'Mako' => 'mako',
|
||||
'Markdown' => 'markdown',
|
||||
'Marko' => 'marko',
|
||||
'Mask' => 'mask',
|
||||
'Mathematica' => 'mathematica',
|
||||
'MATLAB' => 'matlab',
|
||||
'Maven POM' => 'maven pom',
|
||||
'Max' => 'max',
|
||||
'MAXScript' => 'maxscript',
|
||||
'mcfunction' => 'mcfunction',
|
||||
'MediaWiki' => 'mediawiki',
|
||||
'Mercury' => 'mercury',
|
||||
'Meson' => 'meson',
|
||||
'Metal' => 'metal',
|
||||
'Microsoft Developer Studio Project' => 'microsoft developer studio project',
|
||||
'MiniD' => 'minid',
|
||||
'Mirah' => 'mirah',
|
||||
'mIRC Script' => 'mirc script',
|
||||
'MLIR' => 'mlir',
|
||||
'Modelica' => 'modelica',
|
||||
'Modula-2' => 'modula-2',
|
||||
'Modula-3' => 'modula-3',
|
||||
'Module Management System' => 'module management system',
|
||||
'Monkey' => 'monkey',
|
||||
'Moocode' => 'moocode',
|
||||
'MoonScript' => 'moonscript',
|
||||
'Motorola 68K Assembly' => 'motorola 68k assembly',
|
||||
'MQL4' => 'mql4',
|
||||
'MQL5' => 'mql5',
|
||||
'MTML' => 'mtml',
|
||||
'MUF' => 'muf',
|
||||
'mupad' => 'mupad',
|
||||
'Muse' => 'muse',
|
||||
'Myghty' => 'myghty',
|
||||
'nanorc' => 'nanorc',
|
||||
'NASL' => 'nasl',
|
||||
'NCL' => 'ncl',
|
||||
'Nearley' => 'nearley',
|
||||
'Nemerle' => 'nemerle',
|
||||
'nesC' => 'nesc',
|
||||
'NetLinx' => 'netlinx',
|
||||
'NetLinx+ERB' => 'netlinx+erb',
|
||||
'NetLogo' => 'netlogo',
|
||||
'NewLisp' => 'newlisp',
|
||||
'Nextflow' => 'nextflow',
|
||||
'Nginx' => 'nginx',
|
||||
'Nim' => 'nim',
|
||||
'Ninja' => 'ninja',
|
||||
'Nit' => 'nit',
|
||||
'Nix' => 'nix',
|
||||
'NL' => 'nl',
|
||||
'NPM Config' => 'npm config',
|
||||
'NSIS' => 'nsis',
|
||||
'Nu' => 'nu',
|
||||
'NumPy' => 'numpy',
|
||||
'ObjDump' => 'objdump',
|
||||
'Object Data Instance Notation' => 'object data instance notation',
|
||||
'Objective-C' => 'objective-c',
|
||||
'Objective-C++' => 'objective-c++',
|
||||
'Objective-J' => 'objective-j',
|
||||
'ObjectScript' => 'objectscript',
|
||||
'OCaml' => 'ocaml',
|
||||
'Odin' => 'odin',
|
||||
'Omgrofl' => 'omgrofl',
|
||||
'ooc' => 'ooc',
|
||||
'Opa' => 'opa',
|
||||
'Opal' => 'opal',
|
||||
'Open Policy Agent' => 'open policy agent',
|
||||
'OpenCL' => 'opencl',
|
||||
'OpenEdge ABL' => 'openedge abl',
|
||||
'OpenQASM' => 'openqasm',
|
||||
'OpenRC runscript' => 'openrc runscript',
|
||||
'OpenSCAD' => 'openscad',
|
||||
'OpenStep Property List' => 'openstep property list',
|
||||
'OpenType Feature File' => 'opentype feature file',
|
||||
'Org' => 'org',
|
||||
'Ox' => 'ox',
|
||||
'Oxygene' => 'oxygene',
|
||||
'Oz' => 'oz',
|
||||
'P4' => 'p4',
|
||||
'Pan' => 'pan',
|
||||
'Papyrus' => 'papyrus',
|
||||
'Parrot' => 'parrot',
|
||||
'Parrot Assembly' => 'parrot assembly',
|
||||
'Parrot Internal Representation' => 'parrot internal representation',
|
||||
'Pascal' => 'pascal',
|
||||
'Pawn' => 'pawn',
|
||||
'Pep8' => 'pep8',
|
||||
'Perl' => 'perl',
|
||||
'PHP' => 'php',
|
||||
'Pic' => 'pic',
|
||||
'Pickle' => 'pickle',
|
||||
'PicoLisp' => 'picolisp',
|
||||
'PigLatin' => 'piglatin',
|
||||
'Pike' => 'pike',
|
||||
'PLpgSQL' => 'plpgsql',
|
||||
'PLSQL' => 'plsql',
|
||||
'Pod' => 'pod',
|
||||
'Pod 6' => 'pod 6',
|
||||
'PogoScript' => 'pogoscript',
|
||||
'Pony' => 'pony',
|
||||
'PostCSS' => 'postcss',
|
||||
'PostScript' => 'postscript',
|
||||
'POV-Ray SDL' => 'pov-ray sdl',
|
||||
'PowerBuilder' => 'powerbuilder',
|
||||
'PowerShell' => 'powershell',
|
||||
'Prisma' => 'prisma',
|
||||
'Processing' => 'processing',
|
||||
'Proguard' => 'proguard',
|
||||
'Prolog' => 'prolog',
|
||||
'Propeller Spin' => 'propeller spin',
|
||||
'Protocol Buffer' => 'protocol buffer',
|
||||
'Public Key' => 'public key',
|
||||
'Pug' => 'pug',
|
||||
'Puppet' => 'puppet',
|
||||
'Pure Data' => 'pure data',
|
||||
'PureBasic' => 'purebasic',
|
||||
'PureScript' => 'purescript',
|
||||
'Python' => 'python',
|
||||
'Python console' => 'python console',
|
||||
'Python traceback' => 'python traceback',
|
||||
'q' => 'q',
|
||||
'QMake' => 'qmake',
|
||||
'QML' => 'qml',
|
||||
'Quake' => 'quake',
|
||||
'R' => 'r',
|
||||
'Racket' => 'racket',
|
||||
'Ragel' => 'ragel',
|
||||
'Raku' => 'raku',
|
||||
'RAML' => 'raml',
|
||||
'Rascal' => 'rascal',
|
||||
'Raw token data' => 'raw token data',
|
||||
'RDoc' => 'rdoc',
|
||||
'Readline Config' => 'readline config',
|
||||
'REALbasic' => 'realbasic',
|
||||
'Reason' => 'reason',
|
||||
'Rebol' => 'rebol',
|
||||
'Red' => 'red',
|
||||
'Redcode' => 'redcode',
|
||||
'Regular Expression' => 'regular expression',
|
||||
// 'Ren'Py' => 'ren'py',
|
||||
'RenderScript' => 'renderscript',
|
||||
'reStructuredText' => 'restructuredtext',
|
||||
'REXX' => 'rexx',
|
||||
'RHTML' => 'rhtml',
|
||||
'Rich Text Format' => 'rich text format',
|
||||
'Ring' => 'ring',
|
||||
'Riot' => 'riot',
|
||||
'RMarkdown' => 'rmarkdown',
|
||||
'RobotFramework' => 'robotframework',
|
||||
'Roff' => 'roff',
|
||||
'Roff Manpage' => 'roff manpage',
|
||||
'Rouge' => 'rouge',
|
||||
'RPC' => 'rpc',
|
||||
'RPM Spec' => 'rpm spec',
|
||||
'Ruby' => 'ruby',
|
||||
'RUNOFF' => 'runoff',
|
||||
'Rust' => 'rust',
|
||||
'Sage' => 'sage',
|
||||
'SaltStack' => 'saltstack',
|
||||
'SAS' => 'sas',
|
||||
'Sass' => 'sass',
|
||||
'Scala' => 'scala',
|
||||
'Scaml' => 'scaml',
|
||||
'Scheme' => 'scheme',
|
||||
'Scilab' => 'scilab',
|
||||
'SCSS' => 'scss',
|
||||
'sed' => 'sed',
|
||||
'Self' => 'self',
|
||||
'ShaderLab' => 'shaderlab',
|
||||
'Shell' => 'shell',
|
||||
'ShellSession' => 'shellsession',
|
||||
'Shen' => 'shen',
|
||||
'Slash' => 'slash',
|
||||
'Slice' => 'slice',
|
||||
'Slim' => 'slim',
|
||||
'Smali' => 'smali',
|
||||
'Smalltalk' => 'smalltalk',
|
||||
'Smarty' => 'smarty',
|
||||
'SmPL' => 'smpl',
|
||||
'SMT' => 'smt',
|
||||
'Solidity' => 'solidity',
|
||||
'SourcePawn' => 'sourcepawn',
|
||||
'SPARQL' => 'sparql',
|
||||
'Spline Font Database' => 'spline font database',
|
||||
'SQF' => 'sqf',
|
||||
'SQL' => 'sql',
|
||||
'SQLPL' => 'sqlpl',
|
||||
'Squirrel' => 'squirrel',
|
||||
'SRecode Template' => 'srecode template',
|
||||
'SSH Config' => 'ssh config',
|
||||
'Stan' => 'stan',
|
||||
'Standard ML' => 'standard ml',
|
||||
'Starlark' => 'starlark',
|
||||
'Stata' => 'stata',
|
||||
'STON' => 'ston',
|
||||
'Stylus' => 'stylus',
|
||||
'SubRip Text' => 'subrip text',
|
||||
'SugarSS' => 'sugarss',
|
||||
'SuperCollider' => 'supercollider',
|
||||
'Svelte' => 'svelte',
|
||||
'SVG' => 'svg',
|
||||
'Swift' => 'swift',
|
||||
'SWIG' => 'swig',
|
||||
'SystemVerilog' => 'systemverilog',
|
||||
'Tcl' => 'tcl',
|
||||
'Tcsh' => 'tcsh',
|
||||
'Tea' => 'tea',
|
||||
'Terra' => 'terra',
|
||||
'TeX' => 'tex',
|
||||
'Texinfo' => 'texinfo',
|
||||
'Text' => 'text',
|
||||
'Textile' => 'textile',
|
||||
'Thrift' => 'thrift',
|
||||
'TI Program' => 'ti program',
|
||||
'TLA' => 'tla',
|
||||
'TOML' => 'toml',
|
||||
'TSQL' => 'tsql',
|
||||
'TSX' => 'tsx',
|
||||
'Turing' => 'turing',
|
||||
'Turtle' => 'turtle',
|
||||
'Twig' => 'twig',
|
||||
'TXL' => 'txl',
|
||||
'Type Language' => 'type language',
|
||||
'TypeScript' => 'typescript',
|
||||
'Unified Parallel C' => 'unified parallel c',
|
||||
'Unity3D Asset' => 'unity3d asset',
|
||||
'Unix Assembly' => 'unix assembly',
|
||||
'Uno' => 'uno',
|
||||
'UnrealScript' => 'unrealscript',
|
||||
'UrWeb' => 'urweb',
|
||||
'V' => 'v',
|
||||
'Vala' => 'vala',
|
||||
'VBA' => 'vba',
|
||||
'VBScript' => 'vbscript',
|
||||
'VCL' => 'vcl',
|
||||
'Verilog' => 'verilog',
|
||||
'VHDL' => 'vhdl',
|
||||
'Vim script' => 'vim script',
|
||||
'Vim Snippet' => 'vim snippet',
|
||||
'Visual Basic .NET' => 'visual basic .net',
|
||||
'Visual Basic .NET' => 'visual basic .net',
|
||||
'Volt' => 'volt',
|
||||
'Vue' => 'vue',
|
||||
'Wavefront Material' => 'wavefront material',
|
||||
'Wavefront Object' => 'wavefront object',
|
||||
'wdl' => 'wdl',
|
||||
'Web Ontology Language' => 'web ontology language',
|
||||
'WebAssembly' => 'webassembly',
|
||||
'WebIDL' => 'webidl',
|
||||
'WebVTT' => 'webvtt',
|
||||
'Wget Config' => 'wget config',
|
||||
'Windows Registry Entries' => 'windows registry entries',
|
||||
'wisp' => 'wisp',
|
||||
'Wollok' => 'wollok',
|
||||
'World of Warcraft Addon Data' => 'world of warcraft addon data',
|
||||
'X BitMap' => 'x bitmap',
|
||||
'X Font Directory Index' => 'x font directory index',
|
||||
'X PixMap' => 'x pixmap',
|
||||
'X10' => 'x10',
|
||||
'xBase' => 'xbase',
|
||||
'XC' => 'xc',
|
||||
'XCompose' => 'xcompose',
|
||||
'XML' => 'xml',
|
||||
'XML Property List' => 'xml property list',
|
||||
'Xojo' => 'xojo',
|
||||
'XPages' => 'xpages',
|
||||
'XProc' => 'xproc',
|
||||
'XQuery' => 'xquery',
|
||||
'XS' => 'xs',
|
||||
'XSLT' => 'xslt',
|
||||
'Xtend' => 'xtend',
|
||||
'Yacc' => 'yacc',
|
||||
'YAML' => 'yaml',
|
||||
'YANG' => 'yang',
|
||||
'YARA' => 'yara',
|
||||
'YASnippet' => 'yasnippet',
|
||||
'ZAP' => 'zap',
|
||||
'Zeek' => 'zeek',
|
||||
'ZenScript' => 'zenscript',
|
||||
'Zephir' => 'zephir',
|
||||
'Zig' => 'zig',
|
||||
'ZIL' => 'zil',
|
||||
'Zimpl' => 'zimpl',
|
||||
),
|
||||
'defaultValue' => 'All languages'
|
||||
)
|
||||
),
|
||||
|
||||
'global' => array(
|
||||
'date_range' => array(
|
||||
'name' => 'Date range',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Today' => 'today',
|
||||
'Weekly' => 'weekly',
|
||||
'Monthly' => 'monthly',
|
||||
),
|
||||
'defaultValue' => 'today'
|
||||
)
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$params = array('since' => urlencode($this->getInput('date_range')));
|
||||
$url = self::URI . '/' . $this->getInput('language') . '?' . http_build_query($params);
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$this->items = array();
|
||||
foreach($html->find('.Box-row') as $element) {
|
||||
$item = array();
|
||||
|
||||
// URI
|
||||
$item['uri'] = self::URI_ITEM . $element->find('h1 a', 0)->href;
|
||||
|
||||
// Title
|
||||
$item['title'] = str_replace(' ', '', trim(strip_tags($element->find('h1 a', 0)->plaintext)));
|
||||
|
||||
// Description
|
||||
$item['content'] = trim(strip_tags($element->find('p.text-gray', 0)->innertext));
|
||||
|
||||
// Time
|
||||
$item['timestamp'] = time();
|
||||
|
||||
// TODO: Proxy?
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if($this->getInput('language') == '') {
|
||||
return self::NAME . ': all';
|
||||
} elseif (!is_null($this->getInput('language'))) {
|
||||
return self::NAME . ': ' . $this->getInput('language');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -3,34 +3,78 @@ class GizmodoBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'polopollo';
|
||||
const NAME = 'Gizmodo';
|
||||
const URI = 'http://gizmodo.com/';
|
||||
const URI = 'https://gizmodo.com';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the newest posts from Gizmodo (full text).';
|
||||
const DESCRIPTION = 'Returns the newest posts from Gizmodo.';
|
||||
|
||||
protected function parseItem($item){
|
||||
protected function parseItem($item) {
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
if(!$articleHTMLContent) {
|
||||
$text = 'Could not load ' . $item['uri'];
|
||||
} else {
|
||||
$text = $articleHTMLContent->find('div.entry-content', 0)->innertext;
|
||||
foreach($articleHTMLContent->find('pagespeed_iframe') as $element) {
|
||||
$text .= '<p>link to a iframe (could be a video): <a href="'
|
||||
. $element->src
|
||||
. '">'
|
||||
. $element->src
|
||||
. '</a></p><br>';
|
||||
}
|
||||
$html = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Could not request: ' . $item['uri']);
|
||||
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><img><em>');
|
||||
}
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
$this->stripTags($html);
|
||||
$this->handleFigureTags($html);
|
||||
$this->handleIframeTags($html);
|
||||
|
||||
// Get header image
|
||||
$image = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$item['content'] = $html->find('div.js_post-content', 0)->innertext;
|
||||
|
||||
// Get categories
|
||||
$categories = explode(',', $html->find('meta[name="keywords"]', 0)->content);
|
||||
$item['categories'] = array_map('trim', $categories);
|
||||
|
||||
$item['enclosures'][] = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$item['content'] = $text;
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://feeds.gawker.com/gizmodo/full');
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas(self::URI . '/rss', 20);
|
||||
}
|
||||
|
||||
private function stripTags($html) {
|
||||
foreach ($html->find('aside') as $aside) {
|
||||
$aside->outertext = '';
|
||||
}
|
||||
|
||||
foreach ($html->find('div.ad-unit') as $div) {
|
||||
$div->outertext = '';
|
||||
}
|
||||
|
||||
foreach ($html->find('script') as $script) {
|
||||
$script->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
private function handleFigureTags($html) {
|
||||
foreach ($html->find('figure') as $index => $figure) {
|
||||
|
||||
if (isset($figure->attr['data-id'])) {
|
||||
$id = $figure->attr['data-id'];
|
||||
$format = $figure->attr['data-format'];
|
||||
|
||||
} else {
|
||||
$img = $figure->find('img', 0);
|
||||
$id = $img->attr['data-chomp-id'];
|
||||
$format = $img->attr['data-format'];
|
||||
$figure->find('div.img-permalink-sub-wrapper', 0)->style = '';
|
||||
}
|
||||
|
||||
$imageUrl = 'https://i.kinja-img.com/gawker-media/image/upload/' . $id . '.' . $format;
|
||||
|
||||
$figure->find('span', 0)->outertext = <<<EOD
|
||||
<img src="{$imageUrl}">
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIframeTags($html) {
|
||||
foreach($html->find('iframe') as $iframe) {
|
||||
$iframe->src = urljoin($this->getURI(), $iframe->src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,16 +35,10 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
|
||||
// Extract direct URL from google href (eg. /url?q=...)
|
||||
$t = $element->find('a[href]', 0)->href;
|
||||
$item['uri'] = '' . $t;
|
||||
parse_str(parse_url($t, PHP_URL_QUERY), $parameters);
|
||||
if(isset($parameters['q'])) {
|
||||
$item['uri'] = $parameters['q'];
|
||||
}
|
||||
|
||||
$item['uri'] = htmlspecialchars_decode($t);
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('span[class=st]', 0)->plaintext;
|
||||
$item['content'] = $element->find('span[class=aCOpRe]', 0)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
$lastpage = 1;
|
||||
|
||||
for($page = 1; $page <= $lastpage; $page++) {
|
||||
$link = self::URI . '/' . $category . '/page/' . $page;
|
||||
$link = self::URI . $category . '/page/' . $page;
|
||||
$html = getSimpleHTMLDOM($link)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
@@ -41,13 +41,16 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
$lastpage = min($matches[1], ceil($max / 14));
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('.wallpapers .wall a') as $element) {
|
||||
$thumbnail = $element->find('img', 0);
|
||||
|
||||
$search = array(self::URI, 'wallpapers.html');
|
||||
$replace = array(self::URI . 'download/', $this->getInput('r') . '.jpg');
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = self::URI
|
||||
. '/download'
|
||||
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
|
||||
$item['uri'] = str_replace($search, $replace, $element->href);
|
||||
|
||||
$item['timestamp'] = time();
|
||||
$item['title'] = $element->find('em1', 0)->text();
|
||||
@@ -55,7 +58,6 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
. '<br><a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. self::URI
|
||||
. $thumbnail->src
|
||||
. '" /></a>';
|
||||
|
||||
|
47
bridges/HackerNewsUserThreadsBridge.php
Normal file
47
bridges/HackerNewsUserThreadsBridge.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
class HackerNewsUserThreadsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'rakoo';
|
||||
const NAME = 'Hacker News User Threads';
|
||||
const URI = 'https://news.ycombinator.com';
|
||||
const CACHE_TIMEOUT = 7200; // 2 hours
|
||||
const DESCRIPTION = 'Hacker News threads for a user (at https://news.ycombinator.com/threads?id=xxx)';
|
||||
const PARAMETERS = array( array(
|
||||
'user' => array(
|
||||
'name' => 'User',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'User whose threads you want to see'
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$url = 'https://news.ycombinator.com/threads?id=' . $this->getInput('user');
|
||||
$html = getSimpleHTMLDOM($url) or returnServerError('Could not request HN.');
|
||||
Debug::log('queried ' . $url);
|
||||
Debug::log('found ' . $html);
|
||||
|
||||
$item = array();
|
||||
$articles = $html->find('tr[class*="comtr"]');
|
||||
$story = '';
|
||||
|
||||
foreach ($articles as $element) {
|
||||
$id = $element->getAttribute('id');
|
||||
$item['uri'] = 'https://news.ycombinator.com/item?id=' . $id;
|
||||
|
||||
$author = $element->find('span[class*="comhead"]', 0)->find('a[class="hnuser"]', 0)->innertext;
|
||||
$newstory = $element->find('span[class*="comhead"]', 0)->find('span[class="storyon"]', 0);
|
||||
if (count($newstory->find('a')) > 0) {
|
||||
$story = $newstory->find('a', 0)->innertext;
|
||||
}
|
||||
|
||||
$title = $author . ' | on ' . $story;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $element->find('span[class*="age"]', 0)->find('a', 0)->innertext;
|
||||
$item['content'] = $element->find('span[class*="commtext"]', 0)->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -40,18 +40,15 @@ class HeiseBridge extends FeedExpander {
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
$uri = $item['uri'];
|
||||
$uri = $item['uri'] . '&seite=all';
|
||||
|
||||
do {
|
||||
$article = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not open article: ' . $uri);
|
||||
$article = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not open article: ' . $uri);
|
||||
|
||||
if ($article) {
|
||||
$article = defaultLinkTo($article, $uri);
|
||||
$item = $this->addArticleToItem($item, $article);
|
||||
|
||||
if($next = $article->find('.pagination a[rel="next"]', 0))
|
||||
$uri = $next->href;
|
||||
} while ($next);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
@@ -62,6 +59,9 @@ class HeiseBridge extends FeedExpander {
|
||||
|
||||
$content = $article->find('div[class*="article-content"]', 0);
|
||||
|
||||
if ($content == null)
|
||||
$content = $article->find('#article_content', 0);
|
||||
|
||||
foreach($content->find('p, h3, ul, table, pre, img') as $element) {
|
||||
$item['content'] .= $element;
|
||||
}
|
||||
|
114
bridges/IKWYDBridge.php
Normal file
114
bridges/IKWYDBridge.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
class IKWYDBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'DevonHess';
|
||||
const NAME = 'I Know What You Download';
|
||||
const URI = 'https://iknowwhatyoudownload.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns torrent downloads and distributions for an IP address';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'ip' => array(
|
||||
'name' => 'IP Address',
|
||||
'required' => true
|
||||
),
|
||||
'update' => array(
|
||||
'name' => 'Update last seen',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Update timestamp every time "last seen" changes'
|
||||
)
|
||||
)
|
||||
);
|
||||
private $name;
|
||||
private $uri;
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
|
||||
$regex .= '(?:en|ru)\/peer\/\?ip=(\d+\.\d+\.\d+\.\d+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['ip'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
|
||||
$regex .= '(?:(?:en|ru)\/peer\/)?/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['ip'] = $_SERVER['REMOTE_ADDR'];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if($this->name) {
|
||||
return $this->name;
|
||||
} else {
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if($this->uri) {
|
||||
return $this->uri;
|
||||
} else {
|
||||
return self::URI;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$ip = $this->getInput('ip');
|
||||
$root = self::URI . 'en/peer/?ip=' . $ip;
|
||||
$html = getSimpleHTMLDOM($root)
|
||||
or returnServerError('Could not request ' . self::URI);
|
||||
|
||||
$this->name = 'IKWYD: ' . $ip;
|
||||
$this->uri = $root;
|
||||
|
||||
foreach($html->find('.table > tbody > tr') as $download) {
|
||||
$download = defaultLinkTo($download, self::URI);
|
||||
$firstSeen = $download->find('.date-column',
|
||||
0)->innertext;
|
||||
$lastSeen = $download->find('.date-column',
|
||||
1)->innertext;
|
||||
$category = $download->find('.category-column',
|
||||
0)->innertext;
|
||||
$torlink = $download->find('.name-column > div > a',
|
||||
0);
|
||||
$tortitle = strip_tags($torlink);
|
||||
$size = $download->find('td', 4)->innertext;
|
||||
$title = $tortitle;
|
||||
$author = $ip;
|
||||
|
||||
if($this->getInput('update')) {
|
||||
$timestamp = strtotime($lastSeen);
|
||||
} else {
|
||||
$timestamp = strtotime($firstSeen);
|
||||
}
|
||||
|
||||
$uri = $torlink->href;
|
||||
|
||||
$content = 'IP address: <a href="' . $root . '">';
|
||||
$content .= $ip . '</a><br>';
|
||||
$content .= 'First seen: ' . $firstSeen . '<br>';
|
||||
$content .= ($this->getInput('update') ? 'Last seen: ' .
|
||||
$lastSeen . '<br>' : '');
|
||||
$content .= ($category ? 'Category: ' .
|
||||
$category . '<br>' : '');
|
||||
$content .= 'Title: ' . $torlink . '<br>';
|
||||
$content .= 'Size: ' . $size;
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content;
|
||||
if($category) {
|
||||
$item['categories'] = array($category);
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pauder';
|
||||
// const MAINTAINER = 'pauder';
|
||||
const NAME = 'Instagram Bridge';
|
||||
const URI = 'https://www.instagram.com/';
|
||||
const DESCRIPTION = 'Returns the newest images';
|
||||
@@ -47,7 +47,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
const USER_QUERY_HASH = '58b6785bea111c67129decbe6a448951';
|
||||
const TAG_QUERY_HASH = '174a5243287c5f3a7de741089750ab3b';
|
||||
const TAG_QUERY_HASH = '9b498c08113f1e09617a1703c22b2f32';
|
||||
const SHORTCODE_QUERY_HASH = '865589822932d1b43dfe312121dd353a';
|
||||
|
||||
protected function getInstagramUserId($username) {
|
||||
@@ -65,7 +65,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$data = getContents(self::URI . 'web/search/topsearch/?query=' . $username);
|
||||
|
||||
foreach(json_decode($data)->users as $user) {
|
||||
if($user->user->username === $username) {
|
||||
if(strtolower($user->user->username) === strtolower($username)) {
|
||||
$key = $user->user->pk;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
switch($media->__typename) {
|
||||
case 'GraphSidecar':
|
||||
$data = $this->getInstagramSidecarData($item['uri'], $item['title']);
|
||||
$data = $this->getInstagramSidecarData($item['uri'], $item['title'], $media, $textContent);
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
break;
|
||||
@@ -142,7 +142,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$item['enclosures'] = array($mediaURI);
|
||||
break;
|
||||
case 'GraphVideo':
|
||||
$data = $this->getInstagramVideoData($item['uri'], $mediaURI);
|
||||
$data = $this->getInstagramVideoData($item['uri'], $mediaURI, $media, $textContent);
|
||||
$item['content'] = $data[0];
|
||||
if($directLink) {
|
||||
$item['enclosures'] = $data[1];
|
||||
@@ -160,11 +160,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
// returns Sidecar(a post which has multiple media)'s contents and enclosures
|
||||
protected function getInstagramSidecarData($uri, $postTitle) {
|
||||
$mediaInfo = $this->getSinglePostData($uri);
|
||||
|
||||
$textContent = $this->getTextContent($mediaInfo);
|
||||
|
||||
protected function getInstagramSidecarData($uri, $postTitle, $mediaInfo, $textContent) {
|
||||
$enclosures = array();
|
||||
$content = '';
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $singleMedia) {
|
||||
@@ -187,10 +183,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
// returns Video post's contents and enclosures
|
||||
protected function getInstagramVideoData($uri, $mediaURI) {
|
||||
$mediaInfo = $this->getSinglePostData($uri);
|
||||
|
||||
$textContent = $this->getTextContent($mediaInfo);
|
||||
protected function getInstagramVideoData($uri, $mediaURI, $mediaInfo, $textContent) {
|
||||
$content = '<video controls>';
|
||||
$content .= '<source src="' . $mediaInfo->video_url . '" poster="' . $mediaURI . '" type="video/mp4">';
|
||||
$content .= '<img src="' . $mediaURI . '" alt="">';
|
||||
|
@@ -42,7 +42,6 @@ class InternetArchiveBridge extends BridgeAbstract {
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
if ($this->getInput('content') !== 'posts') {
|
||||
|
||||
$detailsDivNumber = 0;
|
||||
|
||||
foreach ($html->find('div.results > div[data-id]') as $index => $result) {
|
||||
@@ -54,7 +53,6 @@ class InternetArchiveBridge extends BridgeAbstract {
|
||||
|
||||
switch($result->class) {
|
||||
case 'item-ia':
|
||||
|
||||
switch($this->getInput('content')) {
|
||||
case 'reviews':
|
||||
$item = $this->processReview($result);
|
||||
@@ -104,7 +102,6 @@ class InternetArchiveBridge extends BridgeAbstract {
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('username')) && !is_null($this->getInput('content'))) {
|
||||
|
||||
$contentValues = array_flip(self::PARAMETERS['Account']['content']['values']);
|
||||
|
||||
return $contentValues[$this->getInput('content')] . ' - '
|
||||
@@ -124,11 +121,10 @@ class InternetArchiveBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function processUpload($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$collection = $result->find('a.stealth', 0);
|
||||
$collectionLink = self::URI . $collection->href;
|
||||
$collectionLink = $collection->href;
|
||||
$collectionTitle = $collection->find('div.item-parent-ttl', 0)->plaintext;
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
|
||||
@@ -150,7 +146,6 @@ EOD;
|
||||
}
|
||||
|
||||
private function processReview($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
|
||||
@@ -172,7 +167,6 @@ EOD;
|
||||
}
|
||||
|
||||
private function processWebArchives($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->plaintext);
|
||||
@@ -189,7 +183,6 @@ EOD;
|
||||
}
|
||||
|
||||
private function processCollection($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$title = trim($result->find('div.collection-title.C.C2', 0)->children(0)->plaintext);
|
||||
@@ -209,7 +202,6 @@ EOD;
|
||||
}
|
||||
|
||||
private function processHiddenDetails($html, $detailsDivNumber, $item) {
|
||||
|
||||
$description = '';
|
||||
|
||||
if ($html->find('div.details-ia.hidden-tiles', $detailsDivNumber)) {
|
||||
@@ -237,7 +229,6 @@ EOD;
|
||||
}
|
||||
|
||||
private function processPosts($html) {
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ($html->find('table.forumTable > tr') as $index => $tr) {
|
||||
@@ -288,6 +279,7 @@ EOD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
46
bridges/ItchioBridge.php
Normal file
46
bridges/ItchioBridge.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
class ItchioBridge extends BridgeAbstract {
|
||||
const NAME = 'itch.io';
|
||||
const URI = 'https://itch.io';
|
||||
const DESCRIPTION = 'Fetches the file uploads for a product';
|
||||
const MAINTAINER = 'jacquesh';
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
'name' => 'Product URL',
|
||||
'exampleValue' => 'https://remedybg.itch.io/remedybg',
|
||||
'required' => true,
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$title = $html->find('.game_title', 0)->innertext;
|
||||
$timestampOriginal = $html->find('span.icon-stopwatch', 0)->parent()->title;
|
||||
$timestampFormatted = str_replace('@', '', $timestampOriginal);
|
||||
|
||||
$content = 'The following files are available to download:<br/>';
|
||||
foreach ($html->find('div.upload') as $element) {
|
||||
$filename = $element->find('strong.name', 0)->innertext;
|
||||
$filesize = $element->find('span.file_size', 0)->first_child()->innertext;
|
||||
$content = $content . $filename . ' (' . $filesize . ')<br/>';
|
||||
}
|
||||
|
||||
// NOTE: At the time of writing it is not clear under which conditions
|
||||
// itch updates the timestamp. In case they don't always update it,
|
||||
// we include the file list as well when computing the UID hash.
|
||||
$uidContent = $timestampFormatted . $content;
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $url;
|
||||
$item['uid'] = $uidContent;
|
||||
$item['title'] = 'New release for ' . $title;
|
||||
$item['content'] = $content;
|
||||
$item['timestamp'] = $timestampFormatted;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
@@ -61,6 +61,8 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
if($html === false)
|
||||
returnServerError('Failed to load page!');
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
// Store header information into private members
|
||||
$this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
|
||||
$this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
|
||||
@@ -93,7 +95,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
$item['content'] = str_replace("\n", '<br>', $item['content']);
|
||||
|
||||
// Fix relative URIs
|
||||
$item['content'] = $this->replaceRelativeURI($item['content']);
|
||||
$item['content'] = $item['content'];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
@@ -125,17 +127,6 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all relative URIs with absolute ones
|
||||
*
|
||||
* @param string $content The source string
|
||||
* @return string Returns the source string with all relative URIs replaced
|
||||
* by absolute ones.
|
||||
*/
|
||||
private function replaceRelativeURI($content){
|
||||
return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds styles as attributes to tags with known classes
|
||||
*
|
||||
|
@@ -3,7 +3,7 @@ class KoreusBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||
const NAME = 'Koreus';
|
||||
const URI = 'http://www.koreus.com/';
|
||||
const URI = 'https://www.koreus.com/';
|
||||
const DESCRIPTION = 'Returns the newest posts from Koreus (full text)';
|
||||
|
||||
protected function parseItem($item){
|
||||
@@ -17,6 +17,6 @@ class KoreusBridge extends FeedExpander {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://feeds.feedburner.com/Koreus-articles');
|
||||
$this->collectExpandableDatas('https://feeds.feedburner.com/Koreus-articles');
|
||||
}
|
||||
}
|
||||
|
@@ -352,12 +352,14 @@ class LeBonCoinBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$url = 'https://api.leboncoin.fr/finder/search/';
|
||||
$url = 'https://api.leboncoin.fr/api/adfinder/v1/search';
|
||||
$data = $this->buildRequestJson();
|
||||
|
||||
$header = array(
|
||||
'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null',
|
||||
'User-Agent: LBC;Android;10;SAMSUNG;phone;0aaaaaaaaaaaaaaa;wifi;8.24.3.8;152437;0',
|
||||
'Content-Type: application/json',
|
||||
'X-LBC-CC: 7',
|
||||
'Accept: application/json,application/hal+json',
|
||||
'Content-Length: ' . strlen($data),
|
||||
'api_key: ' . self::$LBC_API_KEY
|
||||
);
|
||||
|
@@ -26,8 +26,8 @@ class LeMondeInformatiqueBridge extends FeedExpander {
|
||||
|
||||
//No response header sets the encoding, explicit conversion is needed or subsequent xml_encode() will fail
|
||||
$content_node = $article_html->find('div.col-primary, div.col-sm-9', 0);
|
||||
$item['content'] = utf8_encode($this->cleanArticle($content_node->innertext));
|
||||
$item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext);
|
||||
$item['content'] = $this->cleanArticle($content_node->innertext);
|
||||
$item['author'] = $article_html->find('div.author-infos', 0)->find('b', 0)->plaintext;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ class LesJoiesDuCodeBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request LesJoiesDuCode.');
|
||||
|
||||
foreach($html->find('div.blog-post') as $element) {
|
||||
foreach($html->find('article.blog-post') as $element) {
|
||||
$item = array();
|
||||
$temp = $element->find('h1 a', 0);
|
||||
$titre = html_entity_decode($temp->innertext);
|
||||
|
73
bridges/MallTvBridge.php
Normal file
73
bridges/MallTvBridge.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
class MallTvBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'MALL.TV Bridge';
|
||||
const URI = 'https://www.mall.tv';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Return newest videos';
|
||||
const MAINTAINER = 'kolarcz';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'url' => array(
|
||||
'name' => 'url to the show',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.mall.tv/zivot-je-hra'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function fixChars($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function getUploadTimeFromUrl($url) {
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request MALL.TV detail page');
|
||||
|
||||
$scriptLdJson = $html->find('script[type="application/ld+json"]', 0)->innertext;
|
||||
if (!preg_match('/[\'"]uploadDate[\'"]\s*:\s*[\'"](\d{4}-\d{2}-\d{2})[\'"]/', $scriptLdJson, $match)) {
|
||||
returnServerError('Could not get date from MALL.TV detail page');
|
||||
}
|
||||
|
||||
return strtotime($match[1]);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
|
||||
if (!preg_match('/^https:\/\/www\.mall\.tv\/[a-z0-9-]+(\/[a-z0-9-]+)?\/?$/', $url)) {
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request MALL.TV');
|
||||
|
||||
$this->feedUri = $url;
|
||||
$this->feedName = $this->fixChars($html->find('title', 0)->plaintext);
|
||||
|
||||
foreach ($html->find('section.isVideo .video-card') as $element) {
|
||||
$itemTitle = $element->find('.video-card__details-link', 0);
|
||||
$itemThumbnail = $element->find('.video-card__thumbnail', 0);
|
||||
$itemUri = self::URI . $itemTitle->getAttribute('href');
|
||||
|
||||
$item = array(
|
||||
'title' => $this->fixChars($itemTitle->plaintext),
|
||||
'uri' => $itemUri,
|
||||
'content' => '<img src="' . $itemThumbnail->getAttribute('data-src') . '" />',
|
||||
'timestamp' => $this->getUploadTimeFromUrl($itemUri)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return isset($this->feedName) ? $this->feedName : parent::getName();
|
||||
}
|
||||
}
|
127
bridges/MarktplaatsBridge.php
Normal file
127
bridges/MarktplaatsBridge.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
class MarktplaatsBridge extends BridgeAbstract {
|
||||
const NAME = 'Marktplaats';
|
||||
const URI = 'https://marktplaats.nl';
|
||||
const DESCRIPTION = 'Read search queries from marktplaats.nl';
|
||||
const PARAMETERS = array(
|
||||
'Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'query',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'The search string for marktplaats',
|
||||
),
|
||||
'z' => array(
|
||||
'name' => 'zipcode',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'title' => 'Zip code for location limited searches',
|
||||
),
|
||||
'd' => array(
|
||||
'name' => 'distance',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'The distance in meters from the zipcode',
|
||||
),
|
||||
'f' => array(
|
||||
'name' => 'priceFrom',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'The minimal price in cents',
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'priceTo',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'The maximal price in cents',
|
||||
),
|
||||
's' => array(
|
||||
'name' => 'showGlobal',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Include result with negative distance',
|
||||
),
|
||||
'i' => array(
|
||||
'name' => 'includeImage',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Include the image at the end of the content',
|
||||
),
|
||||
'r' => array(
|
||||
'name' => 'includeRaw',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Include the raw data behind the content',
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 900;
|
||||
|
||||
public function collectData() {
|
||||
$query = '';
|
||||
$excludeGlobal = false;
|
||||
if(!is_null($this->getInput('z')) && !is_null($this->getInput('d'))) {
|
||||
$query = '&postcode=' . $this->getInput('z') . '&distanceMeters=' . $this->getInput('d');
|
||||
}
|
||||
if(!is_null($this->getInput('f'))) {
|
||||
$query .= '&PriceCentsFrom=' . $this->getInput('f');
|
||||
}
|
||||
if(!is_null($this->getInput('t'))) {
|
||||
$query .= '&PriceCentsTo=' . $this->getInput('t');
|
||||
}
|
||||
if(!is_null($this->getInput('s'))) {
|
||||
if(!$this->getInput('s')) {
|
||||
$excludeGlobal = true;
|
||||
}
|
||||
}
|
||||
$url = 'https://www.marktplaats.nl/lrp/api/search?query=' . urlencode($this->getInput('q')) . $query;
|
||||
$jsonString = getSimpleHTMLDOM($url, 900) or returnServerError('No contents received!');
|
||||
$jsonObj = json_decode($jsonString);
|
||||
foreach($jsonObj->listings as $listing) {
|
||||
if(!$excludeGlobal || $listing->location->distanceMeters >= 0) {
|
||||
$item = array();
|
||||
$item['uri'] = 'https://marktplaats.nl' . $listing->vipUrl;
|
||||
$item['title'] = $listing->title;
|
||||
$item['timestamp'] = $listing->date;
|
||||
$item['author'] = $listing->sellerInformation->sellerName;
|
||||
$item['content'] = $listing->description;
|
||||
$item['categories'] = $listing->verticals;
|
||||
$item['uid'] = $listing->itemId;
|
||||
if(!is_null($this->getInput('i')) && !empty($listing->imageUrls)) {
|
||||
$item['enclosures'] = $listing->imageUrls;
|
||||
if(is_array($listing->imageUrls)) {
|
||||
foreach($listing->imageUrls as $imgurl) {
|
||||
$item['content'] .= "<br />\n<img src='https:" . $imgurl . "' />";
|
||||
}
|
||||
} else {
|
||||
$item['content'] .= "<br>\n<img src='https:" . $listing->imageUrls . "' />";
|
||||
}
|
||||
}
|
||||
if(!is_null($this->getInput('r'))) {
|
||||
if($this->getInput('r')) {
|
||||
$item['content'] .= "<br />\n<br />\n<br />\n" . json_encode($listing);
|
||||
}
|
||||
}
|
||||
$item['content'] .= "<br>\n<br>\nPrice: " . $listing->priceInfo->priceCents / 100;
|
||||
$item['content'] .= ' (' . $listing->priceInfo->priceType . ')';
|
||||
if(!empty($listing->location->cityName)) {
|
||||
$item['content'] .= "<br><br>\n" . $listing->location->cityName;
|
||||
}
|
||||
if(!is_null($this->getInput('r'))) {
|
||||
if($this->getInput('r')) {
|
||||
$item['content'] .= "<br />\n<br />\n<br />\n" . json_encode($listing);
|
||||
}
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return $this->getInput('q') . ' - Marktplaats';
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -78,7 +78,7 @@ class MastodonBridge extends FeedExpander {
|
||||
|
||||
public function getURI(){
|
||||
if($this->getInput('canusername'))
|
||||
return 'https://' . $this->getInstance() . '/users/' . $this->getUsername() . '.atom';
|
||||
return 'https://' . $this->getInstance() . '/@' . $this->getUsername() . '.rss';
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
48
bridges/MediapartBlogsBridge.php
Normal file
48
bridges/MediapartBlogsBridge.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
class MediapartBlogsBridge extends BridgeAbstract {
|
||||
const NAME = 'Mediapart Blogs';
|
||||
const BASE_URI = 'https://blogs.mediapart.fr';
|
||||
const URI = self::BASE_URI . '/blogs';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'slug' => array(
|
||||
'name' => 'Blog Slug',
|
||||
'type' => 'text',
|
||||
'title' => 'Blog user name',
|
||||
'exampleValue' => 'jean-vincot',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.mediapart.fr/favicon/favicon-club.ico?v=2';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::BASE_URI . '/' . $this->getInput('slug') . '/blog')
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('ul.post-list li') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item_title = $element->find('h3.title a', 0);
|
||||
$item_divs = $element->find('div');
|
||||
|
||||
$item['title'] = $item_title->innertext;
|
||||
$item['uri'] = self::BASE_URI . trim($item_title->href);
|
||||
$item['author'] = $element->find('.author .subscriber', 0)->innertext;
|
||||
$item['content'] = $item_divs[count($item_divs) - 2] . $item_divs[count($item_divs) - 1];
|
||||
$item['timestamp'] = strtotime($element->find('.author time', 0)->datetime);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->getInput('slug')) {
|
||||
return self::NAME . ' | ' . $this->getInput('slug');
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -3,22 +3,26 @@ class MondeDiploBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Pitchoule';
|
||||
const NAME = 'Monde Diplomatique';
|
||||
const URI = 'http://www.monde-diplomatique.fr/';
|
||||
const URI = 'https://www.monde-diplomatique.fr';
|
||||
const CACHE_TIMEOUT = 21600; //6h
|
||||
const DESCRIPTION = 'Returns most recent results from MondeDiplo.';
|
||||
|
||||
private function cleanText($text) {
|
||||
return trim(str_replace(array(' ', ' '), ' ', $text));
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request MondeDiplo. for : ' . self::URI);
|
||||
|
||||
foreach($html->find('div.unarticle') as $article) {
|
||||
$element = $article->parent();
|
||||
$title = $element->find('h3', 0)->plaintext;
|
||||
$datesAuteurs = $element->find('div.dates_auteurs', 0)->plaintext;
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('div.dates_auteurs', 0)->plaintext
|
||||
. '<br>'
|
||||
. strstr($element->find('div', 0)->plaintext, $element->find('div.dates_auteurs', 0)->plaintext, true);
|
||||
$item['uri'] = urljoin(self::URI, $element->href);
|
||||
$item['title'] = $this->cleanText($title) . ' - ' . $this->cleanText($datesAuteurs);
|
||||
$item['content'] = $this->cleanText(str_replace(array($title, $datesAuteurs), '', $element->plaintext));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -61,43 +61,44 @@ class MozillaBugTrackerBridge extends BridgeAbstract {
|
||||
if($html === false)
|
||||
returnServerError('Failed to load page!');
|
||||
|
||||
// Fix relative URLs
|
||||
defaultLinkTo($html, self::URI);
|
||||
|
||||
// Store header information into private members
|
||||
$this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
|
||||
$this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
|
||||
$this->bugid = $html->find('#field-value-bug_id', 0)->plaintext;
|
||||
$this->bugdesc = $html->find('h1#field-value-short_desc', 0)->plaintext;
|
||||
|
||||
// Get and limit comments
|
||||
$comments = $html->find('.bz_comment_table div.bz_comment');
|
||||
$comments = $html->find('div.change-set');
|
||||
|
||||
if($limit > 0 && count($comments) > $limit) {
|
||||
$comments = array_slice($comments, count($comments) - $limit, $limit);
|
||||
}
|
||||
|
||||
// Order comments
|
||||
switch($sorting) {
|
||||
case 'lf': $comments = array_reverse($comments, true);
|
||||
case 'of':
|
||||
default: // Nothing to do, keep original order
|
||||
if ($sorting === 'lf') {
|
||||
$comments = array_reverse($comments, true);
|
||||
}
|
||||
|
||||
foreach($comments as $comment) {
|
||||
$comment = $this->inlineStyles($comment);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '#' . $comment->id;
|
||||
$item['author'] = $comment->find('span.bz_comment_user', 0)->innertext;
|
||||
$item['title'] = $comment->find('span.bz_comment_number', 0)->find('a', 0)->innertext;
|
||||
$item['timestamp'] = strtotime($comment->find('span.bz_comment_time', 0)->innertext);
|
||||
$item['content'] = $comment->find('pre.bz_comment_text', 0)->innertext;
|
||||
$item['uri'] = $comment->find('h3.change-name', 0)->find('a', 0)->href;
|
||||
$item['author'] = $comment->find('td.change-author', 0)->plaintext;
|
||||
$item['title'] = $comment->find('h3.change-name', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($comment->find('span.rel-time', 0)->title);
|
||||
$item['content'] = '';
|
||||
|
||||
// Fix line breaks (they use LF)
|
||||
$item['content'] = str_replace("\n", '<br>', $item['content']);
|
||||
if ($comment->find('.comment-text', 0)) {
|
||||
$item['content'] = $comment->find('.comment-text', 0)->outertext;
|
||||
}
|
||||
|
||||
// Fix relative URIs
|
||||
$item['content'] = $this->replaceRelativeURI($item['content']);
|
||||
if ($comment->find('div.activity', 0)) {
|
||||
$item['content'] .= $comment->find('div.activity', 0)->innertext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
@@ -114,9 +115,8 @@ class MozillaBugTrackerBridge extends BridgeAbstract {
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Bug comments':
|
||||
return 'Bug '
|
||||
. $this->bugid
|
||||
. ' tracker for '
|
||||
return $this->bugid
|
||||
. ' - '
|
||||
. $this->bugdesc
|
||||
. ' - '
|
||||
. parent::getName();
|
||||
@@ -125,17 +125,6 @@ class MozillaBugTrackerBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all relative URIs with absolute ones
|
||||
*
|
||||
* @param string $content The source string
|
||||
* @return string Returns the source string with all relative URIs replaced
|
||||
* by absolute ones.
|
||||
*/
|
||||
private function replaceRelativeURI($content){
|
||||
return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds styles as attributes to tags with known classes
|
||||
*
|
||||
@@ -144,10 +133,14 @@ class MozillaBugTrackerBridge extends BridgeAbstract {
|
||||
* attributes.
|
||||
*/
|
||||
private function inlineStyles($html){
|
||||
foreach($html->find('.bz_obsolete') as $element) {
|
||||
foreach($html->find('.bz_closed') as $element) {
|
||||
$element->style = 'text-decoration:line-through;';
|
||||
}
|
||||
|
||||
foreach($html->find('pre') as $element) {
|
||||
$element->style = 'white-space: pre-wrap;';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
@@ -12,10 +12,8 @@ class NasaApodBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI . 'archivepix.html')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$list = explode('<br>', $html->find('b', 0)->innertext);
|
||||
|
||||
for($i = 0; $i < 3; $i++) {
|
||||
$line = $list[$i];
|
||||
// Start at 1 to skip the "APOD Full Archive" on top of the page
|
||||
for($i = 1; $i < 4; $i++) {
|
||||
$item = array();
|
||||
|
||||
$uri_page = $html->find('a', $i + 3)->href;
|
||||
@@ -26,9 +24,14 @@ class NasaApodBridge extends BridgeAbstract {
|
||||
$picture_html_string = $picture_html->innertext;
|
||||
|
||||
//Extract image and explanation
|
||||
$media = $picture_html->find('p', 1)->innertext;
|
||||
$media = strstr($media, '<br>');
|
||||
$media = preg_replace('/<br>/', '', $media, 1);
|
||||
$image_wrapper = $picture_html->find('a', 1);
|
||||
$image_path = $image_wrapper->href;
|
||||
$img_placeholder = $image_wrapper->find('img', 0);
|
||||
$img_alt = $img_placeholder->alt;
|
||||
$img_style = $img_placeholder->style;
|
||||
$image_uri = self::URI . $image_path;
|
||||
$new_img_placeholder = "<img src=\"$image_uri\" alt=\"$img_alt\" style=\"$img_style\">";
|
||||
$media = "<a href=\"$image_uri\">$new_img_placeholder</a>";
|
||||
$explanation = $picture_html->find('p', 2)->innertext;
|
||||
|
||||
//Extract date from the picture page
|
||||
|
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
class NextInpactBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const MAINTAINER = 'qwertygc and ORelio';
|
||||
const NAME = 'NextInpact Bridge';
|
||||
const URI = 'https://www.nextinpact.com/';
|
||||
const URI_HARDWARE = 'https://www.inpact-hardware.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
@@ -11,10 +12,30 @@ class NextInpactBridge extends FeedExpander {
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tous nos articles' => 'news',
|
||||
'Nos contenus en accès libre' => 'acces-libre',
|
||||
'Blog' => 'blog',
|
||||
'Bons plans' => 'bonsplans'
|
||||
'Nos actualités' => array(
|
||||
'Toutes nos publications' => 'news',
|
||||
'Toutes nos publications sauf #LeBrief' => 'nobrief',
|
||||
'Toutes nos publications sauf INpact Hardware' => 'noih',
|
||||
'Seulement les publications INpact Hardware' => 'hardware:news',
|
||||
'Seulement les publications Next INpact' => 'nobrief-noih',
|
||||
'Seulement les publications #LeBrief' => 'lebrief',
|
||||
),
|
||||
'Flux spécifiques' => array(
|
||||
'Le blog' => 'blog',
|
||||
'Les bons plans' => 'bonsplans',
|
||||
'Publications INpact Hardware en accès libre' => 'hardware:acces-libre',
|
||||
'Publications Next INpact en accès libre' => 'acces-libre',
|
||||
),
|
||||
'Flux thématiques' => array(
|
||||
'Tech' => 'category:1',
|
||||
'Logiciel' => 'category:2',
|
||||
'Internet' => 'category:3',
|
||||
'Mobilité' => 'category:4',
|
||||
'Droit' => 'category:5',
|
||||
'Économie' => 'category:6',
|
||||
'Culture numérique' => 'category:7',
|
||||
'Next INpact' => 'category:8',
|
||||
)
|
||||
)
|
||||
),
|
||||
'filter_premium' => array(
|
||||
@@ -39,9 +60,27 @@ class NextInpactBridge extends FeedExpander {
|
||||
|
||||
public function collectData(){
|
||||
$feed = $this->getInput('feed');
|
||||
if (empty($feed))
|
||||
$base_uri = self::URI;
|
||||
$args = '';
|
||||
|
||||
if (empty($feed)) {
|
||||
// Default to All articles
|
||||
$feed = 'news';
|
||||
$this->collectExpandableDatas(self::URI . 'rss/' . $feed . '.xml');
|
||||
}
|
||||
|
||||
if (strpos($feed, 'hardware:') === 0) {
|
||||
// Feed hosted on Hardware domain
|
||||
$base_uri = self::URI_HARDWARE;
|
||||
$feed = str_replace('hardware:', '', $feed);
|
||||
}
|
||||
|
||||
if (strpos($feed, 'category:') === 0) {
|
||||
// Feed with specific category parameter
|
||||
$args = '?CategoryIds=' . str_replace('category:', '', $feed);
|
||||
$feed = 'params';
|
||||
}
|
||||
|
||||
$this->collectExpandableDatas($base_uri . 'rss/' . $feed . '.xml' . $args);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
@@ -57,9 +96,11 @@ class NextInpactBridge extends FeedExpander {
|
||||
if (!is_object($html))
|
||||
return 'Failed to request NextInpact: ' . $url;
|
||||
|
||||
// Filter premium and brief articles?
|
||||
$brief_selector = 'div.brief-container';
|
||||
foreach(array(
|
||||
'filter_premium' => 'h2.title_reserve_article',
|
||||
'filter_brief' => 'div.brief-inner-content'
|
||||
'filter_premium' => 'p.red-msg',
|
||||
'filter_brief' => $brief_selector
|
||||
) as $param_name => $selector) {
|
||||
$param_val = intval($this->getInput($param_name));
|
||||
if ($param_val != 0) {
|
||||
@@ -71,38 +112,71 @@ class NextInpactBridge extends FeedExpander {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($html->find('div[itemprop=articleBody], div.brief-inner-content', 0))) {
|
||||
$article_content = $html->find('div.article-content', 0);
|
||||
if (!is_object($article_content)) {
|
||||
$article_content = $html->find('div.content', 0);
|
||||
}
|
||||
if (is_object($article_content)) {
|
||||
|
||||
$subtitle = trim($html->find('span.sub_title, div.brief-head', 0));
|
||||
if(is_object($subtitle) && $subtitle->plaintext !== $item['title']) {
|
||||
$subtitle = '<p><em>' . $subtitle->plaintext . '</em></p>';
|
||||
// Subtitle
|
||||
$subtitle = $html->find('small.subtitle', 0);
|
||||
if(!is_object($subtitle) && !is_object($html->find($brief_selector, 0))) {
|
||||
$subtitle = $html->find('small', 0);
|
||||
}
|
||||
if(!is_object($subtitle)) {
|
||||
$content_wrapper = $html->find('div.content-wrapper', 0);
|
||||
if (is_object($content_wrapper)) {
|
||||
$subtitle = $content_wrapper->find('h2.title', 0);
|
||||
}
|
||||
}
|
||||
if(is_object($subtitle) && (!isset($item['title']) || $subtitle->plaintext != $item['title'])) {
|
||||
$subtitle = '<p><em>' . trim($subtitle->plaintext) . '</em></p>';
|
||||
} else {
|
||||
$subtitle = '';
|
||||
}
|
||||
|
||||
$postimg = $html->find(
|
||||
'div.container_main_image_article, div.image-brief-container, div.image-brief-side-container', 0
|
||||
);
|
||||
// Image
|
||||
$postimg = $html->find('div.article-image, div.image-container', 0);
|
||||
if(is_object($postimg)) {
|
||||
$postimg = '<p><img src="'
|
||||
. $postimg->find('img.dedicated', 0)->src
|
||||
. '" alt="-" /></p>';
|
||||
$postimg = $postimg->find('img', 0);
|
||||
if (!empty($postimg->src)) {
|
||||
$postimg = $postimg->src;
|
||||
} else {
|
||||
$postimg = $postimg->srcset; //"url 355w, url 1003w, url 748w"
|
||||
$postimg = explode(', ', $postimg); //split by ', ' to get each url separately
|
||||
$postimg = end($postimg); //Get last item: "url 748w" which is of largest size
|
||||
$postimg = explode(' ', $postimg); //split by ' ' to separate url from res
|
||||
$postimg = array_reverse($postimg); //reverse array content to have url last
|
||||
$postimg = end($postimg); //Get last item of array: "url"
|
||||
}
|
||||
$postimg = '<p><img src="' . $postimg . '" alt="-" /></p>';
|
||||
} else {
|
||||
$postimg = '';
|
||||
}
|
||||
|
||||
// Paywall
|
||||
$paywall = $html->find('div.paywall-restriction', 0);
|
||||
if (is_object($paywall) && is_object($paywall->find('p.red-msg', 0))) {
|
||||
$paywall = '<p><em>' . $paywall->find('span.head-mention', 0)->innertext . '</em></p>';
|
||||
} else {
|
||||
$paywall = '';
|
||||
}
|
||||
|
||||
// Content
|
||||
$article_content = $article_content->outertext;
|
||||
$article_content = str_replace('>Signaler une erreur</span>', '></span>', $article_content);
|
||||
|
||||
// Result
|
||||
$text = $subtitle
|
||||
. $postimg
|
||||
. $html->find('div[itemprop=articleBody], div.brief-inner-content', 0)->outertext;
|
||||
. $article_content
|
||||
. $paywall;
|
||||
|
||||
} else {
|
||||
$text = $item['content']
|
||||
. '<p><em>Failed retrieve full article content</em></p>';
|
||||
}
|
||||
|
||||
$premium_article = $html->find('h2.title_reserve_article', 0);
|
||||
if (is_object($premium_article)) {
|
||||
$text .= '<p><em>' . $premium_article->innertext . '</em></p>';
|
||||
$text = '<p><em>Failed to retrieve full article content</em></p>';
|
||||
if (isset($item['content'])) {
|
||||
$text = $item['content'] . $text;
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
|
@@ -148,7 +148,7 @@ class NineGagBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
if (!$AvoidElement) {
|
||||
$item['uri'] = $post['url'];
|
||||
$item['uri'] = preg_replace('/^http:/i', 'https:', $post['url']);
|
||||
$item['title'] = $post['title'];
|
||||
$item['content'] = self::getContent($post);
|
||||
$item['categories'] = self::getCategories($post);
|
||||
|
131
bridges/NordbayernBridge.php
Normal file
131
bridges/NordbayernBridge.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
ini_set('max_execution_time', '300');
|
||||
class NordbayernBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'schabi.org';
|
||||
const NAME = 'Nordbayern Bridge';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const URI = 'https://www.nordbayern.de';
|
||||
const DESCRIPTION = 'Bridge for Bavarian reginoal news site nordbayern.de';
|
||||
const PARAMETERS = array( array(
|
||||
'region' => array(
|
||||
'name' => 'region',
|
||||
'type' => 'list',
|
||||
'exampleValue' => 'Nürnberg',
|
||||
'title' => 'Select a region',
|
||||
'values' => array(
|
||||
'Nürnberg' => 'nuernberg',
|
||||
'Fürth' => 'fuerth',
|
||||
'Altdorf' => 'altdorf',
|
||||
'Ansbach' => 'ansbach',
|
||||
'Bad Windsheim' => 'bad-windsheim',
|
||||
'Bamberg' => 'bamberg',
|
||||
'Dinkelsbühl/Feuchtwangen' => 'dinkelsbuehl-feuchtwangen',
|
||||
'Feucht' => 'feucht',
|
||||
'Forchheim' => 'forchheim',
|
||||
'Gunzenhausen' => 'gunzenhausen',
|
||||
'Hersbruck' => 'hersbruck',
|
||||
'Herzogenaurach' => 'herzogenaurach',
|
||||
'Hilpoltstein' => 'hilpoltstein',
|
||||
'Höchstadt' => 'hoechstadt',
|
||||
'Lauf' => 'lauf',
|
||||
'Neumarkt' => 'neumarkt',
|
||||
'Neustadt/Aisch' => 'neustadt-aisch',
|
||||
'Pegnitz' => 'pegnitz',
|
||||
'Roth' => 'roth',
|
||||
'Rothenburg o.d.T.' => 'rothenburg-o-d-t',
|
||||
'Schwabach' => 'schwabach',
|
||||
'Treuchtlingen' => 'treuchtlingen',
|
||||
'Weißenburg' => 'weissenburg'
|
||||
)
|
||||
),
|
||||
'policeReports' => array(
|
||||
'name' => 'Police Reports',
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Read Police Reports',
|
||||
)
|
||||
));
|
||||
|
||||
private function getImageUrlFromScript($script) {
|
||||
preg_match(
|
||||
"#src=\\\\'(https:[-:\\.\\\\/a-zA-Z0-9%_]*\\.(jpg|JPG))#",
|
||||
$script->innertext,
|
||||
$matches,
|
||||
PREG_OFFSET_CAPTURE
|
||||
);
|
||||
if(isset($matches[1][0])) {
|
||||
return stripcslashes($matches[1][0]) . '?w=800';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function handleArticle($link) {
|
||||
$item = array();
|
||||
$article = getSimpleHTMLDOM($link);
|
||||
$content = $article->find('div[class*=article-content]', 0);
|
||||
$item['uri'] = $link;
|
||||
$item['title'] = $article->find('h1', 0)->innertext;
|
||||
$item['content'] = '';
|
||||
|
||||
//first get image from block/modul
|
||||
$figure = $article->find('figure[class*=panorama]', 0);
|
||||
if($figure !== null) {
|
||||
$imgUrl = self::getImageUrlFromScript($figure->find('script', 0));
|
||||
if($imgUrl === null) {
|
||||
$imgUrl = self::getImageUrlFromScript($figure->find('script', 1));
|
||||
}
|
||||
$item['content'] .= '<img src="' . $imgUrl . '">';
|
||||
}
|
||||
|
||||
// get regular paragraphs
|
||||
foreach($content->children() as $child) {
|
||||
if($child->tag === 'p') {
|
||||
$item['content'] .= $child;
|
||||
}
|
||||
}
|
||||
|
||||
//get image divs
|
||||
foreach($content->find('div[class*=article-slideshow]') as $slides) {
|
||||
foreach($slides->children() as $child) {
|
||||
switch($child->tag) {
|
||||
case 'p':
|
||||
$item['content'] .= $child;
|
||||
break;
|
||||
case 'h5':
|
||||
$item['content'] .= '<h5><a href="'
|
||||
. self::URI . $child->find('a', 0)->href . '">' . $child->plaintext . '</a></h5>';
|
||||
break;
|
||||
case 'a':
|
||||
$url = self::getImageUrlFromScript($child->find('script', 0));
|
||||
$item['content'] .= '<img src="' . $url . '">';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->items[] = $item;
|
||||
$article->clear();
|
||||
}
|
||||
|
||||
private function handleNewsblock($listSite, $readPoliceReports) {
|
||||
$newsBlocks = $listSite->find('section[class*=newsblock]');
|
||||
$regionalNewsBlock = $newsBlocks[0];
|
||||
$policeBlock = $newsBlocks[1];
|
||||
foreach($regionalNewsBlock->find('h2') as $headline) {
|
||||
self::handleArticle(self::URI . $headline->find('a', 0)->href);
|
||||
}
|
||||
if($readPoliceReports === true) {
|
||||
foreach($policeBlock->find('h2') as $headline) {
|
||||
self::handleArticle(self::URI . $headline->find('a', 0)->href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$item = array();
|
||||
$region = $this->getInput('region');
|
||||
$listSite = getSimpleHTMLDOM(self::URI . '/region/' . $region);
|
||||
|
||||
self::handleNewsblock($listSite, $this->getInput('policeReports'));
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
class NyaaTorrentsBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'NyaaTorrents';
|
||||
@@ -50,6 +50,11 @@ class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
'name' => 'Keyword',
|
||||
'description' => 'Keyword(s)',
|
||||
'type' => 'text'
|
||||
),
|
||||
'u' => array(
|
||||
'name' => 'User',
|
||||
'description' => 'User',
|
||||
'type' => 'text'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -58,74 +63,45 @@ class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
return self::URI . 'static/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(
|
||||
self::URI . '?page=rss&s=id&o=desc&'
|
||||
. http_build_query(array(
|
||||
'f' => $this->getInput('f'),
|
||||
'c' => $this->getInput('c'),
|
||||
'q' => $this->getInput('q'),
|
||||
'u' => $this->getInput('u')
|
||||
)), 20);
|
||||
}
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
$search_url = self::URI . '?s=id&o=desc&'
|
||||
. http_build_query(array(
|
||||
'f' => $this->getInput('f'),
|
||||
'c' => $this->getInput('c'),
|
||||
'q' => $this->getInput('q')
|
||||
));
|
||||
protected function parseItem($newItem){
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url)
|
||||
or returnServerError('Could not request Nyaa: ' . $search_url);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
foreach ($links as $link)
|
||||
if (strpos($link->href, '/view/') === 0 && !in_array($link->href, $results))
|
||||
$results[] = $link->href;
|
||||
if (empty($results) && empty($this->getInput('q')))
|
||||
returnServerError('No results from Nyaa: ' . $url, 500);
|
||||
//Convert URI from torrent file to web page
|
||||
$item['uri'] = str_replace('/download/', '/view/', $item['uri']);
|
||||
$item['uri'] = str_replace('.torrent', '', $item['uri']);
|
||||
|
||||
//Process each item individually
|
||||
foreach ($results as $element) {
|
||||
if ($item_html = getSimpleHTMLDOMCached($item['uri'])) {
|
||||
|
||||
//Limit total amount of requests
|
||||
if(count($this->items) >= 20) {
|
||||
break;
|
||||
}
|
||||
//Retrieve full description from page contents
|
||||
$item_desc = str_get_html(
|
||||
markdownToHtml(html_entity_decode($item_html->find('#torrent-description', 0)->innertext))
|
||||
);
|
||||
|
||||
$torrent_id = str_replace('/view/', '', $element);
|
||||
|
||||
//Ignore entries without valid torrent ID
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'view/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' :: Nyaa', '', $item_html->find('title', 0)->plaintext);
|
||||
$item_desc = str_get_html(markdownToHtml($item_html->find('#torrent-description', 0)->innertext));
|
||||
$item_author = extractFromDelimiters($item_html->outertext, 'href="/user/', '"');
|
||||
$item_date = intval(extractFromDelimiters($item_html->outertext, 'data-timestamp="', '"'));
|
||||
|
||||
//Retrieve image for thumbnail or generic logo fallback
|
||||
$item_image = $this->getURI() . 'static/img/avatar/default.png';
|
||||
foreach ($item_desc->find('img') as $img) {
|
||||
if (strpos($img->src, 'prez') === false) {
|
||||
$item_image = $img->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_desc;
|
||||
$this->items[] = $item;
|
||||
//Retrieve image for thumbnail or generic logo fallback
|
||||
$item_image = $this->getURI() . 'static/img/avatar/default.png';
|
||||
foreach ($item_desc->find('img') as $img) {
|
||||
if (strpos($img->src, 'prez') === false) {
|
||||
$item_image = $img->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$element = null;
|
||||
|
||||
//Add expanded fields to the current item
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_desc;
|
||||
}
|
||||
$results = null;
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
37
bridges/OpenwrtSecurityBridge.php
Normal file
37
bridges/OpenwrtSecurityBridge.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class OpenwrtSecurityBridge extends BridgeAbstract {
|
||||
const NAME = 'OpenWrt Security Advisories';
|
||||
const URI = 'https://openwrt.org/advisory/start';
|
||||
const DESCRIPTION = 'Security Advisories published by openwrt.org';
|
||||
const MAINTAINER = 'mschwld';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const WEBROOT = 'https://openwrt.org';
|
||||
|
||||
public function collectData() {
|
||||
$item = array();
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request entries');
|
||||
|
||||
$advisories = $html->find('div[class=plugin_nspages]', 0);
|
||||
|
||||
foreach($advisories->find('a[class=wikilink1]') as $element) {
|
||||
$item = array();
|
||||
|
||||
$row = $element->innertext;
|
||||
|
||||
$item['title'] = substr($row, 0, strpos($row, ' - '));
|
||||
$item['timestamp'] = $this->getDate($element->href);
|
||||
$item['uri'] = self::WEBROOT . $element->href;
|
||||
$item['uid'] = self::WEBROOT . $element->href;
|
||||
$item['content'] = substr($row, strpos($row, ' - ') + 3);
|
||||
$item['author'] = 'OpenWrt Project';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getDate($href) {
|
||||
$date = substr($href, -12);
|
||||
return $date;
|
||||
}
|
||||
}
|
175
bridges/OtrkeyFinderBridge.php
Normal file
175
bridges/OtrkeyFinderBridge.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
class OtrkeyFinderBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'mibe';
|
||||
const NAME = 'OtrkeyFinder';
|
||||
const URI = 'https://otrkeyfinder.com';
|
||||
const URI_TEMPLATE = 'https://otrkeyfinder.com/en/?search=%s&order=&page=%d';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns the newest .otrkey files matching the search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'searchterm' => array(
|
||||
'name' => 'Search term',
|
||||
'exampleValue' => 'Terminator',
|
||||
'title' => 'The search term is case-insensitive',
|
||||
),
|
||||
'station' => array(
|
||||
'name' => 'Station name',
|
||||
'exampleValue' => 'ARD',
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'any' => '',
|
||||
'Detail' => array(
|
||||
'HD' => 'HD.avi',
|
||||
'AC3' => 'HD.ac3',
|
||||
'HD & AC3' => 'HD.',
|
||||
'HQ' => 'HQ.avi',
|
||||
'AVI' => 'g.avi', // 'g.' to exclude HD.avi and HQ.avi (filename always contains 'mpg.')
|
||||
'MP4' => '.mp4',
|
||||
),
|
||||
),
|
||||
),
|
||||
'minTime' => array(
|
||||
'name' => 'Min. running time',
|
||||
'type' => 'number',
|
||||
'title' => 'The minimum running time in minutes. The resolution is 5 minutes.',
|
||||
'exampleValue' => '90',
|
||||
'defaultValue' => '0',
|
||||
),
|
||||
'maxTime' => array(
|
||||
'name' => 'Max. running time',
|
||||
'type' => 'number',
|
||||
'title' => 'The maximum running time in minutes. The resolution is 5 minutes.',
|
||||
'exampleValue' => '120',
|
||||
'defaultValue' => '0',
|
||||
),
|
||||
'pages' => array(
|
||||
'name' => 'Number of pages',
|
||||
'type' => 'number',
|
||||
'title' => 'Specifies the number of pages to fetch. Increase this value if you get an empty feed.',
|
||||
'exampleValue' => '5',
|
||||
'defaultValue' => '5',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Example: Terminator_20.04.13_02-25_sf2_100_TVOON_DE.mpg.avi.otrkey
|
||||
// The first group is the running time in minutes
|
||||
const FILENAME_REGEX = '/_(\d+)_TVOON_DE\.mpg\..+\.otrkey/';
|
||||
// year.month.day_hour-minute with leading zeros
|
||||
const TIME_REGEX = '/\d{2}\.\d{2}\.\d{2}_\d{2}-\d{2}/';
|
||||
const CONTENT_TEMPLATE = '<ul>%s</ul>';
|
||||
const MIRROR_TEMPLATE = '<li><a href="https://otrkeyfinder.com%s">%s</a></li>';
|
||||
|
||||
public function collectData() {
|
||||
$pages = $this->getInput('pages');
|
||||
|
||||
for($page = 1; $page <= $pages; $page++) {
|
||||
$uri = $this->buildUri($page);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($uri, self::CACHE_TIMEOUT)
|
||||
or returnServerError('Could not request ' . $uri);
|
||||
|
||||
$keys = $html->find('div.otrkey');
|
||||
|
||||
foreach($keys as $key) {
|
||||
$temp = $this->buildItem($key);
|
||||
|
||||
if ($temp != null)
|
||||
$this->items[] = $temp;
|
||||
}
|
||||
|
||||
// Sleep for 0.5 seconds to don't hammer the server.
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildUri($page) {
|
||||
$searchterm = $this->getInput('searchterm');
|
||||
$station = $this->getInput('station');
|
||||
$type = $this->getInput('type');
|
||||
|
||||
// Combine all three parts to a search query by separating them with white space
|
||||
$search = implode(' ', array($searchterm, $station, $type));
|
||||
$search = trim($search);
|
||||
$search = urlencode($search);
|
||||
|
||||
return sprintf(self::URI_TEMPLATE, $search, $page);
|
||||
}
|
||||
|
||||
private function buildItem(simple_html_dom_node $node) {
|
||||
$file = $this->getFilename($node);
|
||||
|
||||
if ($file == null)
|
||||
return null;
|
||||
|
||||
$minTime = $this->getInput('minTime');
|
||||
$maxTime = $this->getInput('maxTime');
|
||||
|
||||
// Do we need to check the running time?
|
||||
if ($minTime != 0 || $maxTime != 0) {
|
||||
if ($maxTime > 0 && $maxTime < $minTime)
|
||||
returnClientError('The minimum running time must be less than the maximum running time.');
|
||||
|
||||
preg_match(self::FILENAME_REGEX, $file, $matches);
|
||||
|
||||
if (!isset($matches[1]))
|
||||
return null;
|
||||
|
||||
$time = (integer)$matches[1];
|
||||
|
||||
// Check for minimum running time
|
||||
if ($minTime > 0 && $minTime > $time)
|
||||
return null;
|
||||
|
||||
// Check for maximum running time
|
||||
if ($maxTime > 0 && $maxTime < $time)
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $file;
|
||||
|
||||
// The URI_TEMPLATE for querying the site can be reused here
|
||||
$item['uri'] = sprintf(self::URI_TEMPLATE, $file, 1);
|
||||
|
||||
$content = $this->buildContent($node);
|
||||
|
||||
if ($content != null)
|
||||
$item['content'] = $content;
|
||||
|
||||
if (preg_match(self::TIME_REGEX, $file, $matches) === 1) {
|
||||
$item['timestamp'] = DateTime::createFromFormat(
|
||||
'y.m.d_H-i',
|
||||
$matches[0],
|
||||
new DateTimeZone('Europe/Berlin')
|
||||
)->getTimestamp();
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getFilename(simple_html_dom_node $node) {
|
||||
$file = $node->find('.file', 0);
|
||||
|
||||
if ($file == null)
|
||||
return null;
|
||||
else
|
||||
return trim($file->innertext);
|
||||
}
|
||||
|
||||
private function buildContent(simple_html_dom_node $node) {
|
||||
$mirrors = $node->find('div.mirror');
|
||||
$list = '';
|
||||
|
||||
// Build list of available mirrors
|
||||
foreach($mirrors as $mirror) {
|
||||
$anchor = $mirror->find('a', 0);
|
||||
$list .= sprintf(self::MIRROR_TEMPLATE, $anchor->href, $anchor->innertext);
|
||||
}
|
||||
|
||||
return sprintf(self::CONTENT_TEMPLATE, $list);
|
||||
}
|
||||
}
|
@@ -123,17 +123,25 @@ class PikabuBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
$title = $post->find('.story__title-link', 0);
|
||||
$title_element = $post->find('.story__title-link', 0);
|
||||
|
||||
$title = $title_element->plaintext;
|
||||
$community_link = $post->find('.story__community-link', 0);
|
||||
// adding special marker for "Maybe News" section
|
||||
// these posts are fake
|
||||
if (!is_null($community_link) && $community_link->getAttribute('href') == '/community/maybenews') {
|
||||
$title = '[' . $community_link->innertext . '] ' . $title;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['categories'] = $categories;
|
||||
$item['author'] = $post->find('.user__nick', 0)->innertext;
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['title'] = $title;
|
||||
$item['content'] = strip_tags(
|
||||
backgroundToImg($post->find('.story__content-inner', 0)->innertext),
|
||||
'<br><p><img><a>
|
||||
');
|
||||
$item['uri'] = $title->href;
|
||||
$item['uri'] = $title_element->href;
|
||||
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
45
bridges/PresidenciaPTBridge.php
Normal file
45
bridges/PresidenciaPTBridge.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class PresidenciaPTBridge extends BridgeAbstract {
|
||||
const NAME = 'Presidência da República Portuguesa';
|
||||
const URI = 'https://www.presidencia.pt';
|
||||
const DESCRIPTION = 'Presidência da República Portuguesa | Mensagens';
|
||||
const MAINTAINER = 'somini';
|
||||
|
||||
const PT_MONTH_NAMES = array(
|
||||
'janeiro',
|
||||
'fevereiro',
|
||||
'março',
|
||||
'abril',
|
||||
'maio',
|
||||
'junho',
|
||||
'julho',
|
||||
'agosto',
|
||||
'setembro',
|
||||
'outubro',
|
||||
'novembro',
|
||||
'dezembro');
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/atualidade/mensagens')
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('#atualidade-list article.card-block') as $element) {
|
||||
$item = array();
|
||||
|
||||
$link = $element->find('a', 0);
|
||||
$etitle = $link->find('h2', 0);
|
||||
$edts = $element->find('p', 1);
|
||||
$edt = html_entity_decode($edts->innertext, ENT_HTML5);
|
||||
|
||||
$item['title'] = $etitle->innertext;
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
$item['description'] = $element;
|
||||
$item['timestamp'] = str_ireplace(
|
||||
array_map(function($name) { return ' de ' . $name . ' de '; }, self::PT_MONTH_NAMES),
|
||||
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
|
||||
$edt);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
56
bridges/RaceDepartmentBridge.php
Normal file
56
bridges/RaceDepartmentBridge.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
class RaceDepartmentBridge extends FeedExpander {
|
||||
const NAME = 'RaceDepartment News';
|
||||
const URI = 'https://racedepartment.com/';
|
||||
const DESCRIPTION = 'Get the latest (sim)racing news from RaceDepartment.';
|
||||
const MAINTAINER = 't0stiman';
|
||||
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas('https://www.racedepartment.com/news/archive.rss', 10);
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
//fetch page
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
|
||||
or returnServerError('Could not retrieve ' . $feedItem->link);
|
||||
//extract article
|
||||
$item['content'] = $articlePage->find('div.thfeature_firstPost', 0);
|
||||
|
||||
//convert iframes to links. meant for embedded videos.
|
||||
foreach($item['content']->find('iframe') as $found) {
|
||||
|
||||
$iframeUrl = $found->getAttribute('src');
|
||||
|
||||
if ($iframeUrl) {
|
||||
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
//get rid of some elements we don't need
|
||||
$to_remove_selectors = array(
|
||||
'div.p-title', //title
|
||||
'ul.listInline', //Thread starter, Start date
|
||||
'div.rd_news_article_share_buttons',
|
||||
'div.thfeature_firstPost-author',
|
||||
'div.reactionsBar',
|
||||
'footer',
|
||||
'div.message-lastEdit',
|
||||
'section.message-attachments'
|
||||
);
|
||||
|
||||
foreach($to_remove_selectors as $selector) {
|
||||
foreach($item['content']->find($selector) as $found) {
|
||||
$found->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
//category
|
||||
$forumPath = $articlePage->find('div.breadcrumb', 0);
|
||||
$pathElements = $forumPath->find('span');
|
||||
$item['categories'] = array(end($pathElements)->innertext);
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ class RadioMelodieBridge extends BridgeAbstract {
|
||||
$picture = array();
|
||||
|
||||
// Get the Main picture URL
|
||||
$picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
|
||||
$picture[] = self::URI . $article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src;
|
||||
$audioHTML = $article->find('audio');
|
||||
|
||||
// Add the audio element to the enclosure
|
||||
|
@@ -12,8 +12,8 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$dlUrl = 'https://www.ubisoft.com/api/updates/items?categoriesFilter=all';
|
||||
$dlUrl = $dlUrl . '&limit=6&mediaFilter=all&skip=0&startIndex=undefined&locale=en-us';
|
||||
$dlUrl = 'https://www.ubisoft.com/api/updates/items?locale=en-us&categoriesFilter=all';
|
||||
$dlUrl = $dlUrl . '&limit=6&mediaFilter=news&skip=0&startIndex=undefined&tags=BR-rainbow-six%20GA-siege';
|
||||
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = json_decode($jsonString, true);
|
||||
@@ -27,34 +27,7 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
||||
$uri = $uri . $jsonItem['button']['buttonUrl'];
|
||||
|
||||
$thumbnail = '<img src="' . $jsonItem['thumbnail']['url'] . '" alt="Thumbnail">';
|
||||
$content = $thumbnail . '<br />' . $jsonItem['content'];
|
||||
|
||||
// Markdown parsing from https://gist.github.com/jbroadway/2836900
|
||||
|
||||
// Line breaks
|
||||
$content = preg_replace("/\r\n|\r|\n/", '<br/>', $content);
|
||||
|
||||
// Links
|
||||
$regex = '/\[([^\[]+)\]\(([^\)]+)\)/';
|
||||
$replacement = '<a href=\'\2\'>\1</a>';
|
||||
$content = preg_replace($regex, $replacement, $content);
|
||||
|
||||
// Bold text
|
||||
$regex = '/(\*\*|__)(.*?)\1/';
|
||||
$replacement = '<strong>\2</strong>';
|
||||
$content = preg_replace($regex, $replacement, $content);
|
||||
|
||||
// Lists
|
||||
$regex = '/\n\s*[\*|\-](.*)/';
|
||||
$content = preg_replace_callback($regex, function($regs) {
|
||||
$item = $regs[1];
|
||||
return sprintf ('<ul><li>%s</li></ul>', trim ($item));
|
||||
}, $content);
|
||||
|
||||
// Italic text
|
||||
$regex = '/(\*\*|\*)(.*?)\1/';
|
||||
$replacement = '<i>\2</i>';
|
||||
$content = preg_replace($regex, $replacement, $content);
|
||||
$content = $thumbnail . '<br />' . markdownToHtml($jsonItem['content']);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $uri;
|
||||
|
@@ -1,12 +1,22 @@
|
||||
<?php
|
||||
class RedditBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'leomaradan';
|
||||
class RedditBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'dawidsowa';
|
||||
const NAME = 'Reddit Bridge';
|
||||
const URI = 'https://www.reddit.com/';
|
||||
const DESCRIPTION = 'Reddit RSS Feed fixer';
|
||||
const URI = 'https://www.reddit.com';
|
||||
const DESCRIPTION = 'Return hot submissions from Reddit';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'score' => array(
|
||||
'name' => 'Minimal score',
|
||||
'required' => false,
|
||||
'type' => 'number',
|
||||
'exampleValue' => 100,
|
||||
'title' => 'Filter out posts with lower score'
|
||||
)
|
||||
),
|
||||
'single' => array(
|
||||
'r' => array(
|
||||
'name' => 'SubReddit',
|
||||
@@ -22,19 +32,221 @@ class RedditBridge extends FeedExpander {
|
||||
'exampleValue' => 'selfhosted, php',
|
||||
'title' => 'SubReddit names, separated by commas'
|
||||
)
|
||||
),
|
||||
'user' => array(
|
||||
'u' => array(
|
||||
'name' => 'User',
|
||||
'required' => true,
|
||||
'title' => 'User name'
|
||||
),
|
||||
'comments' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Comments',
|
||||
'title' => 'Whether to return comments',
|
||||
'defaultValue' => false
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
public function detectParameters($url) {
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'single': $subreddits[] = $this->getInput('r'); break;
|
||||
case 'multi': $subreddits = explode(',', $this->getInput('rs')); break;
|
||||
if ($parsed_url['host'] != 'www.reddit.com' && $parsed_url['host'] != 'old.reddit.com') return null;
|
||||
|
||||
$path = explode('/', $parsed_url['path']);
|
||||
|
||||
if ($path[1] == 'r') {
|
||||
return array(
|
||||
'r' => $path[2]
|
||||
);
|
||||
} elseif ($path[1] == 'user') {
|
||||
return array(
|
||||
'u' => $path[2]
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.redditstatic.com/desktop2x/img/favicon/favicon-96x96.png';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->queriedContext == 'single') {
|
||||
return 'Reddit r/' . $this->getInput('r');
|
||||
} elseif ($this->queriedContext == 'user') {
|
||||
return 'Reddit u/' . $this->getInput('u');
|
||||
} else {
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$user = false;
|
||||
$comments = false;
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'single':
|
||||
$subreddits[] = $this->getInput('r');
|
||||
break;
|
||||
case 'multi':
|
||||
$subreddits = explode(',', $this->getInput('rs'));
|
||||
break;
|
||||
case 'user':
|
||||
$subreddits[] = $this->getInput('u');
|
||||
$user = true;
|
||||
$comments = $this->getInput('comments');
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($subreddits as $subreddit) {
|
||||
$name = trim($subreddit);
|
||||
$this->collectExpandableDatas("https://www.reddit.com/r/$name/.rss");
|
||||
|
||||
$values = getContents(self::URI . ($user ? '/user/' : '/r/') . $name . '.json')
|
||||
or returnServerError('Unable to fetch posts!');
|
||||
$decodedValues = json_decode($values);
|
||||
|
||||
foreach ($decodedValues->data->children as $post) {
|
||||
if ($post->kind == 't1' && !$comments) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $post->data;
|
||||
|
||||
if ($data->score < $this->getInput('score')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $data->author;
|
||||
$item['uid'] = $data->id;
|
||||
$item['timestamp'] = $data->created_utc;
|
||||
$item['uri'] = $this->encodePermalink($data->permalink);
|
||||
|
||||
$item['categories'] = array();
|
||||
|
||||
if ($post->kind == 't1') {
|
||||
$item['title'] = 'Comment: ' . $data->link_title;
|
||||
} else {
|
||||
$item['title'] = $data->title;
|
||||
|
||||
$item['categories'][] = $data->link_flair_text;
|
||||
$item['categories'][] = $data->pinned ? 'Pinned' : null;
|
||||
$item['categories'][] = $data->spoiler ? 'Spoiler' : null;
|
||||
}
|
||||
|
||||
$item['categories'][] = $data->over_18 ? 'NSFW' : null;
|
||||
$item['categories'] = array_filter($item['categories']);
|
||||
|
||||
if ($post->kind == 't1') {
|
||||
// Comment
|
||||
|
||||
$item['content']
|
||||
= htmlspecialchars_decode($data->body_html);
|
||||
|
||||
} elseif ($data->is_self) {
|
||||
// Text post
|
||||
|
||||
$item['content']
|
||||
= htmlspecialchars_decode($data->selftext_html);
|
||||
|
||||
} elseif (isset($data->post_hint) ? $data->post_hint == 'link' : false) {
|
||||
// Link with preview
|
||||
|
||||
if (isset($data->media)) {
|
||||
// Reddit embeds content for some sites (e.g. Twitter)
|
||||
$embed = htmlspecialchars_decode(
|
||||
$data->media->oembed->html
|
||||
);
|
||||
} else {
|
||||
$embed = '';
|
||||
}
|
||||
|
||||
$item['content'] = $this->template(
|
||||
$data->url,
|
||||
$data->thumbnail,
|
||||
$data->domain
|
||||
) . $embed;
|
||||
|
||||
} elseif (isset($data->post_hint) ? $data->post_hint == 'image' : false) {
|
||||
// Single image
|
||||
|
||||
$item['content'] = $this->link(
|
||||
$this->encodePermalink($data->permalink),
|
||||
'<img src="' . $data->url . '" />'
|
||||
);
|
||||
|
||||
} elseif (isset($data->is_gallery) ? $data->is_gallery : false) {
|
||||
// Multiple images
|
||||
|
||||
$images = array();
|
||||
foreach ($data->gallery_data->items as $media) {
|
||||
$id = $media->media_id;
|
||||
$type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u';
|
||||
$src = $data->media_metadata->$id->s->$type;
|
||||
$images[] = '<figure><img src="' . $src . '"/></figure>';
|
||||
}
|
||||
|
||||
$item['content'] = implode('', $images);
|
||||
|
||||
} elseif ($data->is_video) {
|
||||
// Video
|
||||
|
||||
// Higher index -> Higher resolution
|
||||
end($data->preview->images[0]->resolutions);
|
||||
$index = key($data->preview->images[0]->resolutions);
|
||||
|
||||
$item['content'] = $this->template(
|
||||
$data->url,
|
||||
$data->preview->images[0]->resolutions[$index]->url,
|
||||
'Video'
|
||||
);
|
||||
|
||||
} elseif (isset($data->media) ? $data->media->type == 'youtube.com' : false) {
|
||||
// Youtube link
|
||||
|
||||
$item['content'] = $this->template(
|
||||
$data->url,
|
||||
$data->media->oembed->thumbnail_url,
|
||||
'YouTube');
|
||||
|
||||
} elseif (explode('.', $data->domain)[0] == 'self') {
|
||||
// Crossposted text post
|
||||
// TODO (optionally?) Fetch content of the original post.
|
||||
|
||||
$item['content'] = $this->link(
|
||||
$this->encodePermalink($data->permalink),
|
||||
'Crossposted from r/'
|
||||
. explode('.', $data->domain)[1]
|
||||
);
|
||||
|
||||
} else {
|
||||
// Link WITHOUT preview
|
||||
|
||||
$item['content'] = $this->link($data->url, $data->domain);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function encodePermalink($link) {
|
||||
return self::URI . implode(
|
||||
'/',
|
||||
array_map('urlencode', explode('/', $link))
|
||||
);
|
||||
}
|
||||
|
||||
private function template($href, $src, $caption) {
|
||||
return '<a href="' . $href . '"><figure><figcaption>'
|
||||
. $caption . '</figcaption><img src="'
|
||||
. $src . '"/></figure></a>';
|
||||
}
|
||||
|
||||
private function link($href, $text) {
|
||||
return '<a href="' . $href . '">' . $text . '</a>';
|
||||
}
|
||||
}
|
||||
|
@@ -5,13 +5,16 @@ class Releases3DSBridge extends BridgeAbstract {
|
||||
const NAME = '3DS Scene Releases';
|
||||
const URI = 'http://www.3dsdb.com/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the newest scene releases.';
|
||||
const DESCRIPTION = 'Returns the newest scene releases for Nintendo 3DS.';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectDataUrl(self::URI . 'xml.php');
|
||||
}
|
||||
|
||||
protected function collectDataUrl($dataUrl){
|
||||
|
||||
$dataUrl = self::URI . 'xml.php';
|
||||
$xml = getContents($dataUrl)
|
||||
or returnServerError('Could not request 3dsdb: ' . $dataUrl);
|
||||
or returnServerError('Could not request URL: ' . $dataUrl);
|
||||
$limit = 0;
|
||||
|
||||
foreach(array_reverse(explode('<release>', $xml)) as $element) {
|
||||
@@ -52,17 +55,25 @@ class Releases3DSBridge extends BridgeAbstract {
|
||||
|
||||
$ignSearchUrl = 'https://www.ign.com/search?q=' . urlencode($name);
|
||||
if($ignResult = getSimpleHTMLDOMCached($ignSearchUrl)) {
|
||||
$ignCoverArt = $ignResult->find('div.search-item-media', 0)->find('img', 0)->src;
|
||||
$ignDesc = $ignResult->find('div.search-item-description', 0)->plaintext;
|
||||
$ignLink = $ignResult->find('div.search-item-sub-title', 0)->find('a', 1)->href;
|
||||
$ignDate = strtotime(trim($ignResult->find('span.publish-date', 0)->plaintext));
|
||||
$ignDescription = '<div><img src="'
|
||||
. $ignCoverArt
|
||||
. '" /></div><div>'
|
||||
. $ignDesc
|
||||
. ' <a href="'
|
||||
. $ignLink
|
||||
. '">More at IGN</a></div>';
|
||||
$ignCoverArt = $ignResult->find('div.search-item-media', 0);
|
||||
$ignDesc = $ignResult->find('div.search-item-description', 0);
|
||||
$ignLink = $ignResult->find('div.search-item-sub-title', 0);
|
||||
$ignDate = $ignResult->find('span.publish-date', 0);
|
||||
if (is_object($ignCoverArt))
|
||||
$ignCoverArt = $ignCoverArt->find('img', 0);
|
||||
if (is_object($ignLink))
|
||||
$ignLink = $ignLink->find('a', 1);
|
||||
if (is_object($ignDate))
|
||||
$ignDate = strtotime(trim($ignDate->plaintext));
|
||||
if (is_object($ignCoverArt) && is_object($ignDesc) && is_object($ignLink)) {
|
||||
$ignDescription = '<div><img src="'
|
||||
. $ignCoverArt->src
|
||||
. '" /></div><div>'
|
||||
. $ignDesc->plaintext
|
||||
. ' <a href="'
|
||||
. $ignLink->href
|
||||
. '">More at IGN</a></div>';
|
||||
}
|
||||
}
|
||||
|
||||
//Main section : Release description from 3DS database
|
||||
@@ -111,7 +122,7 @@ class Releases3DSBridge extends BridgeAbstract {
|
||||
|
||||
private function typeToString($type){
|
||||
switch($type) {
|
||||
case 1: return '3DS Game';
|
||||
case 1: return 'Card Game';
|
||||
case 4: return 'eShop';
|
||||
default: return '??? (' . $type . ')';
|
||||
}
|
||||
|
17
bridges/ReleasesSwitchBridge.php
Normal file
17
bridges/ReleasesSwitchBridge.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
// This bridge depends on Releases3DSBridge
|
||||
if (!class_exists('Releases3DSBridge')) {
|
||||
include('Releases3DSBridge.php');
|
||||
}
|
||||
|
||||
class ReleasesSwitchBridge extends Releases3DSBridge {
|
||||
|
||||
const NAME = 'Switch Scene Releases';
|
||||
const URI = 'http://www.nswdb.com/';
|
||||
const DESCRIPTION = 'Returns the newest scene releases for Nintendo Switch.';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectDataUrl(self::URI . 'xml.php');
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ class ReporterreBridge extends BridgeAbstract {
|
||||
|
||||
private function extractContent($url){
|
||||
$html2 = getSimpleHTMLDOM($url);
|
||||
$html2 = defaultLinkTo($html2, self::URI);
|
||||
|
||||
foreach($html2->find('div[style=text-align:justify]') as $e) {
|
||||
$text = $e->outertext;
|
||||
@@ -16,13 +17,6 @@ class ReporterreBridge extends BridgeAbstract {
|
||||
$html2->clear();
|
||||
unset($html2);
|
||||
|
||||
// Replace all relative urls with absolute ones
|
||||
$text = preg_replace(
|
||||
'/(href|src)(\=[\"\'])(?!http)([^"\']+)/ims',
|
||||
'$1$2' . self::URI . '$3',
|
||||
$text
|
||||
);
|
||||
|
||||
$text = strip_tags($text, '<p><br><a><img>');
|
||||
return $text;
|
||||
}
|
||||
|
261
bridges/ReutersBridge.php
Normal file
261
bridges/ReutersBridge.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
class ReutersBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'hollowleviathan, spraynard, csisoap';
|
||||
const NAME = 'Reuters Bridge';
|
||||
const URI = 'https://reuters.com/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns news from Reuters';
|
||||
|
||||
private $feedName = self::NAME;
|
||||
|
||||
/**
|
||||
* Wireitem types allowed in the final story output
|
||||
*/
|
||||
const ALLOWED_WIREITEM_TYPES = array(
|
||||
'story',
|
||||
'headlines'
|
||||
);
|
||||
|
||||
/**
|
||||
* Wireitem template types allowed in the final story output
|
||||
*/
|
||||
const ALLOWED_TEMPLATE_TYPES = array(
|
||||
'story',
|
||||
'headlines'
|
||||
);
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'feed' => array(
|
||||
'name' => 'News Feed',
|
||||
'type' => 'list',
|
||||
'title' => 'Feeds from Reuters U.S/International edition',
|
||||
'values' => array(
|
||||
'Aerospace and Defense' => 'aerospace',
|
||||
'Business' => 'business',
|
||||
'China' => 'china',
|
||||
'Energy' => 'energy',
|
||||
'Entertainment' => 'chan:8ym8q8dl',
|
||||
'Environment' => 'chan:6u4f0jgs',
|
||||
'Health' => 'chan:8hw7807a',
|
||||
'Lifestyle' => 'life',
|
||||
'Markets' => 'markets',
|
||||
'Politics' => 'politics',
|
||||
'Science' => 'science',
|
||||
'Special Reports' => 'special-reports',
|
||||
'Sports' => 'sports',
|
||||
'Tech' => 'tech',
|
||||
'Top News' => 'home/topnews',
|
||||
'UK' => 'chan:61leiu7j',
|
||||
'USA News' => 'us',
|
||||
'Wire' => 'wire',
|
||||
'World' => 'world',
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Performs an HTTP request to the Reuters API and returns decoded JSON
|
||||
* in the form of an associative array
|
||||
* @param string $feed_uri Parameter string to the Reuters API
|
||||
* @return array
|
||||
*/
|
||||
private function getJson($feed_uri)
|
||||
{
|
||||
$uri = "https://wireapi.reuters.com/v8$feed_uri";
|
||||
$returned_data = getContents($uri);
|
||||
return json_decode($returned_data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in data from Reuters Wire API and
|
||||
* creates structured data in the form of a list
|
||||
* of story information.
|
||||
* @param array $data JSON collected from the Reuters Wire API
|
||||
*/
|
||||
private function processData($data)
|
||||
{
|
||||
/**
|
||||
* Gets a list of wire items which are groups of templates
|
||||
*/
|
||||
$reuters_allowed_wireitems = array_filter(
|
||||
$data, function ($wireitem) {
|
||||
return in_array(
|
||||
$wireitem['wireitem_type'],
|
||||
self::ALLOWED_WIREITEM_TYPES
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Gets a list of "Templates", which is data containing a story
|
||||
*/
|
||||
$reuters_wireitem_templates = array_reduce(
|
||||
$reuters_allowed_wireitems,
|
||||
function (array $carry, array $wireitem) {
|
||||
$wireitem_templates = $wireitem['templates'];
|
||||
return array_merge(
|
||||
$carry,
|
||||
array_filter(
|
||||
$wireitem_templates, function (
|
||||
array $template_data
|
||||
) {
|
||||
return in_array(
|
||||
$template_data['type'],
|
||||
self::ALLOWED_TEMPLATE_TYPES
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
return $reuters_wireitem_templates;
|
||||
}
|
||||
|
||||
private function getArticle($feed_uri)
|
||||
{
|
||||
// This will make another request to API to get full detail of article and author's name.
|
||||
$rawData = $this->getJson($feed_uri);
|
||||
$reuters_wireitems = $rawData['wireitems'];
|
||||
$processedData = $this->processData($reuters_wireitems);
|
||||
|
||||
$first = reset($processedData);
|
||||
$article_content = $first['story']['body_items'];
|
||||
$authorlist = $first['story']['authors'];
|
||||
$category = $first['story']['channel']['name'];
|
||||
$image_list = $first['story']['images'];
|
||||
|
||||
$content_detail = array(
|
||||
'content' => $this->handleArticleContent($article_content),
|
||||
'author' => $this->handleAuthorName($authorlist),
|
||||
'category' => $category,
|
||||
'images' => $this->handleImage($image_list),
|
||||
);
|
||||
return $content_detail;
|
||||
}
|
||||
|
||||
private function handleImage($images) {
|
||||
$img_placeholder = '';
|
||||
|
||||
foreach($images as $image) { // Add more image to article.
|
||||
$image_url = $image['url'];
|
||||
$image_caption = $image['caption'];
|
||||
$img = "<img src=\"$image_url\">";
|
||||
$img_caption = "<figcaption style=\"text-align: center;\"><i>$image_caption</i></figcaption>";
|
||||
$figure = "<figure>$img \t $img_caption</figure>";
|
||||
$img_placeholder = $img_placeholder . $figure;
|
||||
}
|
||||
|
||||
return $img_placeholder;
|
||||
}
|
||||
|
||||
private function handleAuthorName($authors) {
|
||||
$author = '';
|
||||
$counter = 0;
|
||||
foreach ($authors as $data) {
|
||||
//Formatting author's name.
|
||||
$counter++;
|
||||
$name = $data['name'];
|
||||
if ($counter == count($authors)) {
|
||||
$author = $author . $name;
|
||||
} else {
|
||||
$author = $author . "$name, ";
|
||||
}
|
||||
}
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
private function handleArticleContent($contents) {
|
||||
$description = '';
|
||||
foreach ($contents as $content) {
|
||||
$data;
|
||||
if(isset($content['content'])) {
|
||||
$data = $content['content'];
|
||||
}
|
||||
switch($content['type']) {
|
||||
case 'paragraph':
|
||||
$description = $description . "<p>$data</p>";
|
||||
break;
|
||||
case 'heading':
|
||||
$description = $description . "<h3>$data</h3>";
|
||||
break;
|
||||
case 'infographics':
|
||||
$description = $description . "<img src=\"$data\">";
|
||||
break;
|
||||
case 'inline_items':
|
||||
$item_list = $content['items'];
|
||||
$description = $description . '<p>';
|
||||
foreach ($item_list as $item) {
|
||||
if($item['type'] == 'text') {
|
||||
$description = $description . $item['content'];
|
||||
} else {
|
||||
$description = $description . $item['symbol'];
|
||||
}
|
||||
}
|
||||
$description = $description . '</p>';
|
||||
break;
|
||||
case 'p_table':
|
||||
$description = $description . $content['content'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->feedName;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$reuters_feed_name = $this->getInput('feed');
|
||||
|
||||
if(strpos($reuters_feed_name, 'chan:') !== false) {
|
||||
// Now checking whether that feed has unique ID or not.
|
||||
$feed_uri = "/feed/rapp/us/wirefeed/$reuters_feed_name";
|
||||
} else {
|
||||
$feed_uri = "/feed/rapp/us/tabbar/feeds/$reuters_feed_name";
|
||||
}
|
||||
|
||||
$data = $this->getJson($feed_uri);
|
||||
|
||||
$reuters_wireitems = $data['wireitems'];
|
||||
$this->feedName = $data['wire_name'] . ' | Reuters';
|
||||
$processedData = $this->processData($reuters_wireitems);
|
||||
|
||||
// Merge all articles from Editor's Highlight section into existing array of templates.
|
||||
$top_section = reset($processedData);
|
||||
if ($top_section['type'] == 'headlines') {
|
||||
$top_section = array_shift($processedData);
|
||||
$articles = $top_section['headlines'];
|
||||
$processedData = array_merge($articles, $processedData);
|
||||
}
|
||||
|
||||
foreach ($processedData as $story) {
|
||||
$item['uid'] = $story['story']['usn'];
|
||||
$article_uri = $story['template_action']['api_path'];
|
||||
$content_detail = $this->getArticle($article_uri);
|
||||
$description = $content_detail['content'];
|
||||
$author = $content_detail['author'];
|
||||
$images = $content_detail['images'];
|
||||
$item['categories'] = array($content_detail['category']);
|
||||
$item['author'] = $author;
|
||||
if (!(bool) $description) {
|
||||
$description = $story['story']['lede']; // Just in case the content doesn't have anything.
|
||||
} else {
|
||||
$item['content'] = "$description $images";
|
||||
}
|
||||
|
||||
$item['title'] = $story['story']['hed'];
|
||||
$item['timestamp'] = $story['story']['updated_at'];
|
||||
$item['uri'] = $story['template_action']['url'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
27
bridges/RobinhoodSnacksBridge.php
Normal file
27
bridges/RobinhoodSnacksBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
class RobinhoodSnacksBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'johnpc';
|
||||
const NAME = 'Robinhood Snacks Newsletter';
|
||||
const URI = 'https://snacks.robinhood.com/newsletters/';
|
||||
const CACHE_TIMEOUT = 86400; // 24h
|
||||
const DESCRIPTION = 'Returns newsletters from Robinhood Snacks';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request snacks.robinhood.com.');
|
||||
|
||||
foreach ($html->find('#root > div > div > div > div > div > a') as $element) {
|
||||
if ($element->href === 'https://snacks.robinhood.com/newsletters/page/2/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->items[] = array(
|
||||
'uri' => $element->href,
|
||||
'title' => $element->find('div > div', 3)->plaintext,
|
||||
'content' => $element->find('div > div', 4)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,12 +8,14 @@ class Rule34pahealBridge extends Shimmie2Bridge {
|
||||
const URI = 'https://rule34.paheal.net/';
|
||||
const DESCRIPTION = 'Returns images from given page';
|
||||
|
||||
const PATHTODATA = '.shm-thumb';
|
||||
|
||||
protected function getItemFromElement($element){
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . $element->href;
|
||||
$item['uri'] = rtrim($this->getURI(), '/') . $element->find('.shm-thumb-link', 0)->href;
|
||||
$item['id'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
$thumbnailUri = $element->find('a', 1)->href;
|
||||
$item['tags'] = $element->getAttribute('data-tags');
|
||||
$item['title'] = $this->getName() . ' | ' . $item['id'];
|
||||
$item['content'] = '<a href="'
|
||||
|
@@ -3,15 +3,11 @@ class SensCritiqueBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'kranack';
|
||||
const NAME = 'Sens Critique';
|
||||
const URI = 'http://www.senscritique.com/';
|
||||
const URI = 'https://www.senscritique.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Sens Critique news';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'm' => array(
|
||||
'name' => 'Movies',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
's' => array(
|
||||
'name' => 'Series',
|
||||
'type' => 'checkbox'
|
||||
@@ -40,8 +36,6 @@ class SensCritiqueBridge extends BridgeAbstract {
|
||||
if($this->getInput($category)) {
|
||||
$uri = self::URI;
|
||||
switch($category) {
|
||||
case 'm': $uri .= 'films/cette-semaine';
|
||||
break;
|
||||
case 's': $uri .= 'series/actualite';
|
||||
break;
|
||||
case 'g': $uri .= 'jeuxvideo/actualite';
|
||||
@@ -77,20 +71,25 @@ class SensCritiqueBridge extends BridgeAbstract {
|
||||
. ' '
|
||||
. $movie->find('.elco-date', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<em>'
|
||||
. $movie->find('.elco-original-title', 0)->plaintext
|
||||
. '</em><br><br>'
|
||||
. $movie->find('.elco-baseline', 0)->plaintext
|
||||
$item['content'] = '';
|
||||
$originalTitle = $movie->find('.elco-original-title', 0);
|
||||
$description = $movie->find('.elco-description', 0);
|
||||
|
||||
if ($originalTitle) {
|
||||
$item['content'] = '<em>' . $originalTitle->plaintext . '</em><br><br>';
|
||||
}
|
||||
|
||||
$item['content'] .= $movie->find('.elco-baseline', 0)->plaintext
|
||||
. '<br>'
|
||||
. $movie->find('.elco-baseline', 1)->plaintext
|
||||
. '<br><br>'
|
||||
. $movie->find('.elco-description', 0)->plaintext
|
||||
. ($description ? $description->plaintext : '')
|
||||
. '<br><br>'
|
||||
. trim($movie->find('.erra-ratings .erra-global', 0)->plaintext)
|
||||
. ' / 10';
|
||||
|
||||
$item['id'] = $this->getURI() . $movie->find('.elco-title a', 0)->href;
|
||||
$item['uri'] = $this->getURI() . $movie->find('.elco-title a', 0)->href;
|
||||
$item['id'] = $this->getURI() . ltrim($movie->find('.elco-title a', 0)->href, '/');
|
||||
$item['uri'] = $this->getURI() . ltrim($movie->find('.elco-title a', 0)->href, '/');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
91
bridges/SeznamZpravyBridge.php
Normal file
91
bridges/SeznamZpravyBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class SeznamZpravyBridge extends BridgeAbstract {
|
||||
const NAME = 'Seznam Zprávy Bridge';
|
||||
const URI = 'https://seznamzpravy.cz';
|
||||
const DESCRIPTION = 'Returns newest stories from Seznam Zprávy';
|
||||
const MAINTAINER = 'thezeroalpha';
|
||||
const PARAMETERS = array(
|
||||
'By Author' => array(
|
||||
'author' => array(
|
||||
'name' => 'Author String',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'The dash-separated author string, as shown in the URL bar.',
|
||||
'pattern' => '[a-z]+-[a-z]+-[0-9]+',
|
||||
'exampleValue' => 'janek-rubes-506'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
private $feedName;
|
||||
|
||||
public function getName() {
|
||||
if (isset($this->feedName)) {
|
||||
return $this->feedName;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$ONE_DAY = 86500;
|
||||
switch($this->queriedContext) {
|
||||
case 'By Author':
|
||||
$url = 'https://www.seznamzpravy.cz/autor/';
|
||||
$selectors = array(
|
||||
'breadcrumbs' => 'div[data-dot=ogm-breadcrumb-navigation]',
|
||||
'article_list' => 'ul.ogm-document-timeline-page.atm-list-ul li article[data-dot=mol-timeline-item]',
|
||||
'article_title' => 'a[data-dot=mol-article-card-title]',
|
||||
'article_dm' => 'span.mol-formatted-date__date',
|
||||
'article_time' => 'span.mol-formatted-date__time',
|
||||
'article_content' => 'div[data-dot=ogm-article-content]'
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($url . $this->getInput('author'), $ONE_DAY);
|
||||
$main_breadcrumbs = $html->find($selectors['breadcrumbs'], 0);
|
||||
$author = $main_breadcrumbs->last_child()->plaintext
|
||||
or returnServerError('Could not get author on: ' . $this->getURI());
|
||||
$this->feedName = $author . ' - Seznam Zprávy';
|
||||
|
||||
$articles = $html->find($selectors['article_list'])
|
||||
or returnServerError('Could not find articles on: ' . $this->getURI());
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$title_link = $article->find($selectors['article_title'], 0)
|
||||
or returnServerError('Could not find title on: ' . $this->getURI());
|
||||
|
||||
$article_url = $title_link->href;
|
||||
$article_content_html = getSimpleHTMLDOMCached($article_url, $ONE_DAY);
|
||||
$content_e = $article_content_html->find($selectors['article_content'], 0);
|
||||
$content_text = $content_e->innertext
|
||||
or returnServerError('Could not get article content for: ' . $article_url);
|
||||
|
||||
$breadcrumbs_e = $article_content_html->find($selectors['breadcrumbs'], 0);
|
||||
$breadcrumbs = $breadcrumbs_e->children();
|
||||
$num_breadcrumbs = count($breadcrumbs);
|
||||
$categories = array();
|
||||
foreach ($breadcrumbs as $cat) {
|
||||
if (--$num_breadcrumbs <= 0) {
|
||||
break;
|
||||
}
|
||||
$categories[] = trim($cat->plaintext);
|
||||
}
|
||||
|
||||
$article_dm_e = $article->find($selectors['article_dm'], 0);
|
||||
$article_dm_text = $article_dm_e->plaintext;
|
||||
$article_dmy = preg_replace('/[^0-9\.]/', '', $article_dm_text) . date('Y');
|
||||
$article_time = $article->find($selectors['article_time'], 0)->plaintext;
|
||||
$item = array(
|
||||
'title' => $title_link->plaintext,
|
||||
'uri' => $title_link->href,
|
||||
'timestamp' => strtotime($article_dmy . ' ' . $article_time),
|
||||
'author' => $author,
|
||||
'content' => $content_text,
|
||||
'categories' => $categories
|
||||
);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
@@ -455,6 +455,35 @@ class SkimfeedBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
|
||||
if (0 !== strpos($url, static::URI)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach(self::PARAMETERS as $channels) {
|
||||
|
||||
foreach($channels as $box_name => $box) {
|
||||
|
||||
foreach($box['values'] as $name => $channel_url) {
|
||||
|
||||
if (static::URI . $channel_url === $url) {
|
||||
return array(
|
||||
$box_name => $name,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
@@ -11,44 +11,60 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'type',
|
||||
'type' => 'list',
|
||||
'defaultValue' => 'tracks',
|
||||
'values' => array(
|
||||
'Tracks' => 'tracks',
|
||||
'Playlists' => 'playlists'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
private $feedTitle = null;
|
||||
private $feedIcon = null;
|
||||
private $clientIDCache = null;
|
||||
|
||||
private $clientIdRegex = '/client_id.*?"(.+?)"/';
|
||||
private $widgetRegex = '/widget-.+?\.js/';
|
||||
|
||||
public function collectData(){
|
||||
$res = $this->apiGet('resolve', array(
|
||||
'url' => 'http://www.soundcloud.com/' . $this->getInput('u')
|
||||
'url' => 'https://soundcloud.com/' . $this->getInput('u')
|
||||
)) or returnServerError('No results for this query');
|
||||
|
||||
$this->feedTitle = $res->username;
|
||||
$this->feedIcon = $res->avatar_url;
|
||||
|
||||
$tracks = $this->apiGet('users/' . urlencode($res->id) . '/tracks')
|
||||
or returnServerError('No results for this user');
|
||||
$tracks = $this->apiGet(
|
||||
'users/' . urlencode($res->id) . '/' . $this->getInput('t'),
|
||||
array('limit' => 31)
|
||||
) or returnServerError('No results for this user/playlist');
|
||||
|
||||
$numTracks = min(count($tracks), 10);
|
||||
for($i = 0; $i < $numTracks; $i++) {
|
||||
foreach ($tracks->collection as $index => $track) {
|
||||
$item = array();
|
||||
$item['author'] = $tracks[$i]->user->username;
|
||||
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
||||
$item['timestamp'] = strtotime($tracks[$i]->created_at);
|
||||
$item['content'] = $tracks[$i]->description;
|
||||
$item['enclosures'] = array($tracks[$i]->uri
|
||||
. '/stream?client_id='
|
||||
. $this->getClientID());
|
||||
$item['author'] = $track->user->username;
|
||||
$item['title'] = $track->user->username . ' - ' . $track->title;
|
||||
$item['timestamp'] = strtotime($track->created_at);
|
||||
$item['content'] = nl2br($track->description);
|
||||
$item['enclosures'][] = $track->artwork_url;
|
||||
|
||||
$item['id'] = self::URI
|
||||
. urlencode($this->getInput('u'))
|
||||
. '/'
|
||||
. urlencode($tracks[$i]->permalink);
|
||||
. urlencode($track->permalink);
|
||||
$item['uri'] = self::URI
|
||||
. urlencode($this->getInput('u'))
|
||||
. '/'
|
||||
. urlencode($tracks[$i]->permalink);
|
||||
. urlencode($track->permalink);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon(){
|
||||
@@ -64,8 +80,8 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return $this->getInput('u') . ' - ' . self::NAME;
|
||||
if($this->feedTitle) {
|
||||
return $this->feedTitle . ' - ' . self::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
@@ -100,30 +116,41 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
// Without url=http, this returns a 404
|
||||
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
|
||||
or returnServerError('Unable to get player page.');
|
||||
$regex = '/widget-.+?\.js/';
|
||||
if(preg_match($regex, $playerHTML, $matches) == false)
|
||||
|
||||
// Extract widget JS filenames from player page
|
||||
if(preg_match_all($this->widgetRegex, $playerHTML, $matches) == false)
|
||||
returnServerError('Unable to find widget JS URL.');
|
||||
$widgetURL = 'https://widget.sndcdn.com/' . $matches[0];
|
||||
|
||||
$widgetJS = getContents($widgetURL)
|
||||
or returnServerError('Unable to get widget JS page.');
|
||||
$regex = '/client_id.*?"(.+?)"/';
|
||||
if(preg_match($regex, $widgetJS, $matches) == false)
|
||||
$clientID = '';
|
||||
|
||||
// Loop widget js files and extract client ID
|
||||
foreach ($matches[0] as $widgetFile) {
|
||||
$widgetURL = 'https://widget.sndcdn.com/' . $widgetFile;
|
||||
|
||||
$widgetJS = getContents($widgetURL)
|
||||
or returnServerError('Unable to get widget JS page.');
|
||||
|
||||
if(preg_match($this->clientIdRegex, $widgetJS, $matches)) {
|
||||
$clientID = $matches[1];
|
||||
$this->clientIDCache->saveData($clientID);
|
||||
|
||||
return $clientID;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($clientID)) {
|
||||
returnServerError('Unable to find client ID.');
|
||||
$clientID = $matches[1];
|
||||
|
||||
$this->clientIDCache->saveData($clientID);
|
||||
return $clientID;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildAPIURL($endpoint, $parameters){
|
||||
return 'https://api.soundcloud.com/'
|
||||
return 'https://api-v2.soundcloud.com/'
|
||||
. $endpoint
|
||||
. '?'
|
||||
. http_build_query($parameters);
|
||||
}
|
||||
|
||||
private function apiGet($endpoint, $parameters = array()){
|
||||
private function apiGet($endpoint, $parameters = array()) {
|
||||
$parameters['client_id'] = $this->getClientID();
|
||||
|
||||
try {
|
||||
|
34
bridges/SymfonyCastsBridge.php
Normal file
34
bridges/SymfonyCastsBridge.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
class SymfonyCastsBridge extends BridgeAbstract {
|
||||
const NAME = 'SymfonyCasts Bridge';
|
||||
const URI = 'https://symfonycasts.com/';
|
||||
const DESCRIPTION = 'Follow new updates on symfonycasts.com';
|
||||
const MAINTAINER = 'Park0';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM('https://symfonycasts.com/updates/find')
|
||||
or returnServerError('Unable to get page.');
|
||||
$dives = $html->find('div');
|
||||
|
||||
/* @var simple_html_dom $div */
|
||||
foreach ($dives as $div) {
|
||||
$id = $div->getAttribute('data-mark-update-id-value');
|
||||
$type = $div->find('h5', 0);
|
||||
$title = $div->find('span', 0);
|
||||
$dateString = $div->find('h5.font-gray', 0);
|
||||
$href = $div->find('a', 0);
|
||||
$url = 'https://symfonycasts.com' . $href->getAttribute('href');
|
||||
|
||||
$item = array(); // Create an empty item
|
||||
$item['uid'] = $id;
|
||||
$item['title'] = $title->innertext;
|
||||
$item['timestamp'] = $dateString->innertext;
|
||||
$item['content'] = $type->plaintext . '<a href="' . $url . '">' . $title . '</a>';
|
||||
$item['uri'] = $url;
|
||||
$this->items[] = $item; // Add item to the list
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -21,6 +21,18 @@ class TelegramBridge extends BridgeAbstract {
|
||||
private $itemTitle = '';
|
||||
|
||||
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
|
||||
private $detectParamsRegex = '/^https?:\/\/t.me\/(?:s\/)?([\w]+)$/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
if(preg_match($this->detectParamsRegex, $url, $matches) > 0) {
|
||||
$params['username'] = $matches[1];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
@@ -39,8 +51,8 @@ class TelegramBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->processUri($messageDiv);
|
||||
$item['content'] = html_entity_decode($this->processContent($messageDiv), ENT_QUOTES);
|
||||
$item['title'] = html_entity_decode($this->itemTitle, ENT_QUOTES);
|
||||
$item['content'] = $this->processContent($messageDiv);
|
||||
$item['title'] = $this->itemTitle;
|
||||
$item['timestamp'] = $this->processDate($messageDiv);
|
||||
$item['enclosures'] = $this->enclosures;
|
||||
$author = trim($messageDiv->find('a.tgme_widget_message_owner_name', 0)->plaintext);
|
||||
@@ -120,6 +132,12 @@ class TelegramBridge extends BridgeAbstract {
|
||||
$messageDiv->find('div.tgme_widget_message_text.js-message_text', 0)->plaintext
|
||||
);
|
||||
}
|
||||
if ($messageDiv->find('div.tgme_widget_message_document', 0)) {
|
||||
$message .= 'Attachments:';
|
||||
foreach ($messageDiv->find('div.tgme_widget_message_document') as $attachments) {
|
||||
$message .= $attachments->find('div.tgme_widget_message_document_title.accent_color', 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ($messageDiv->find('a.tgme_widget_message_link_preview', 0)) {
|
||||
$message .= $this->processLinkPreview($messageDiv);
|
||||
|
@@ -11,14 +11,14 @@ class TheCodingLoveBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request The Coding Love.');
|
||||
|
||||
foreach($html->find('div.post') as $element) {
|
||||
foreach($html->find('article.blog-post') as $element) {
|
||||
$item = array();
|
||||
$temp = $element->find('h3 a', 0);
|
||||
$temp = $element->find('h1 a', 0);
|
||||
|
||||
$titre = $temp->innertext;
|
||||
$title = $temp->innertext;
|
||||
$url = $temp->href;
|
||||
|
||||
$temp = $element->find('div.bodytype', 0);
|
||||
$temp = $element->find('div.blog-post-content', 0);
|
||||
|
||||
// retrieve .gif instead of static .jpg
|
||||
$images = $temp->find('p.e img');
|
||||
@@ -28,17 +28,13 @@ class TheCodingLoveBridge extends BridgeAbstract {
|
||||
}
|
||||
$content = $temp->innertext;
|
||||
|
||||
$auteur = $temp->find('i', 0);
|
||||
$pos = strpos($auteur->innertext, 'by');
|
||||
|
||||
if($pos > 0) {
|
||||
$auteur = trim(str_replace('*/', '', substr($auteur->innertext, ($pos + 2))));
|
||||
$item['author'] = $auteur;
|
||||
}
|
||||
$temp = $element->find('div.post-meta-info', 0);
|
||||
$author = $temp->find('span', 0);
|
||||
$item['author'] = $author->innertext;
|
||||
|
||||
$item['content'] .= trim($content);
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = trim($titre);
|
||||
$item['title'] = trim($title);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
51
bridges/TheFarSideBridge.php
Normal file
51
bridges/TheFarSideBridge.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
class TheFarSideBridge extends BridgeAbstract {
|
||||
const NAME = 'The Far Side Bridge';
|
||||
const URI = 'https://www.thefarside.com';
|
||||
const DESCRIPTION = 'Returns the daily dose';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array();
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request: ' . self::URI);
|
||||
|
||||
$div = $html->find('div.tfs-page-container__cows', 0);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $html->find('meta[property="og:url"]', 0)->content;
|
||||
$item['title'] = $div->find('h3', 0)->innertext;
|
||||
$item['timestamp'] = $div->find('h3', 0)->innertext;
|
||||
$item['content'] = '';
|
||||
|
||||
foreach($div->find('div.card-body') as $index => $card) {
|
||||
$image = $card->find('img', 0);
|
||||
$imageUrl = $image->attr['data-src'];
|
||||
|
||||
// Images are downloaded to bypass the hotlink protection.
|
||||
$image = getContents($imageUrl, array('Referer: ' . self::URI))
|
||||
or returnServerError('Could not request: ' . $imageUrl);
|
||||
|
||||
// Encode image as base64
|
||||
$imageBase64 = base64_encode($image);
|
||||
|
||||
$caption = '';
|
||||
|
||||
if ($card->find('figcaption', 0)) {
|
||||
$caption = $card->find('figcaption', 0)->innertext;
|
||||
}
|
||||
|
||||
$item['content'] .= <<<EOD
|
||||
<figure>
|
||||
<img title="{$caption}" src="data:image/jpeg;base64,{$imageBase64}"/>
|
||||
<figcaption>{$caption}</figcaption>
|
||||
</figure>
|
||||
<br/>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
@@ -17,6 +17,7 @@ class TheHackerNewsBridge extends BridgeAbstract {
|
||||
|
||||
$article_url = $element->find('a.story-link', 0)->href;
|
||||
$article_author = trim($element->find('i.icon-user', 0)->parent()->plaintext);
|
||||
$article_author = str_replace('', '', $article_author);
|
||||
$article_title = $element->find('h2.home-title', 0)->plaintext;
|
||||
|
||||
//Date without time
|
||||
|
@@ -12,7 +12,7 @@ class TheYeteeBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request The Yetee.');
|
||||
|
||||
$div = $html->find('.hero-col');
|
||||
$div = $html->find('.module_timed-item.is--full');
|
||||
foreach($div as $element) {
|
||||
|
||||
$item = array();
|
||||
@@ -21,16 +21,15 @@ class TheYeteeBridge extends BridgeAbstract {
|
||||
$title = $element->find('h2', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
$author = trim($element->find('div[class=credit]', 0)->plaintext);
|
||||
$author = trim($element->find('.module_timed-item--artist a', 0)->plaintext);
|
||||
$item['author'] = $author;
|
||||
|
||||
$uri = $element->find('div[class=controls] a', 0)->href;
|
||||
$item['uri'] = static::URI . $uri;
|
||||
$item['uri'] = static::URI;
|
||||
|
||||
$content = '<p>' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '</p>';
|
||||
$photos = $element->find('a[class=js-modaal-gallery] img');
|
||||
$content = '<p>' . $title . ' by ' . $author . '</p>';
|
||||
$photos = $element->find('a.img');
|
||||
foreach($photos as $photo) {
|
||||
$content = $content . "<br /><img src='$photo->src' />";
|
||||
$content = $content . "<br /><img src='$photo->href' />";
|
||||
$item['enclosures'][] = $photo->src;
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
155
bridges/TwitScoopBridge.php
Normal file
155
bridges/TwitScoopBridge.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
class TwitScoopBridge extends BridgeAbstract {
|
||||
const NAME = 'TwitScoop Bridge';
|
||||
const URI = 'https://www.twitscoop.com';
|
||||
const DESCRIPTION = 'Returns trending Twitter topics by country';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'country' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Worldwide' => 'worldwide',
|
||||
'Algeria' => 'algeria',
|
||||
'Argentina' => 'argentina',
|
||||
'Australia' => 'australia',
|
||||
'Austria' => 'austria',
|
||||
'Bahrain' => 'bahrain',
|
||||
'Belarus' => 'belarus',
|
||||
'Belgium' => 'belgium',
|
||||
'Brazil' => 'brazil',
|
||||
'Canada' => 'canada',
|
||||
'Chile' => 'chile',
|
||||
'Colombia' => 'colombia',
|
||||
'Denmark' => 'denmark',
|
||||
'Dominican Republic' => 'dominican-republic',
|
||||
'Ecuador' => 'ecuador',
|
||||
'Egypt' => 'egypt',
|
||||
'France' => 'france',
|
||||
'Germany' => 'germany',
|
||||
'Ghana' => 'ghana',
|
||||
'Greece' => 'greece',
|
||||
'Guatemala' => 'guatemala',
|
||||
'India' => 'india',
|
||||
'Indonesia' => 'indonesia',
|
||||
'Ireland' => 'ireland',
|
||||
'Israel' => 'israel',
|
||||
'Italy' => 'italy',
|
||||
'Japan' => 'japan',
|
||||
'Jordan' => 'jordan',
|
||||
'Kenya' => 'kenya',
|
||||
'Korea' => 'korea',
|
||||
'Kuwait' => 'kuwait',
|
||||
'Latvia' => 'latvia',
|
||||
'Lebanon' => 'lebanon',
|
||||
'Malaysia' => 'malaysia',
|
||||
'Mexico' => 'mexico',
|
||||
'Netherlands' => 'netherlands',
|
||||
'New Zealand' => 'new-zealand',
|
||||
'Nigeria' => 'nigeria',
|
||||
'Norway' => 'norway',
|
||||
'Oman' => 'oman',
|
||||
'Pakistan' => 'pakistan',
|
||||
'Panama' => 'panama',
|
||||
'Peru' => 'peru',
|
||||
'Philippines' => 'philippines',
|
||||
'Poland' => 'poland',
|
||||
'Portugal' => 'portugal',
|
||||
'Puerto Rico' => 'puerto-rico',
|
||||
'Qatar' => 'qatar',
|
||||
'Russia' => 'russia',
|
||||
'Saudi Arabia' => 'saudi-arabia',
|
||||
'Singapore' => 'singapore',
|
||||
'South Africa' => 'south-africa',
|
||||
'Spain' => 'spain',
|
||||
'Sweden' => 'sweden',
|
||||
'Switzerland' => 'switzerland',
|
||||
'Thailand' => 'thailand',
|
||||
'Turkey' => 'turkey',
|
||||
'Ukraine' => 'ukraine',
|
||||
'United Arab Emirates' => 'united-arab-emirates',
|
||||
'United Kingdom' => 'united-kingdom',
|
||||
'United States' => 'united-states',
|
||||
'Venezuela' => 'venezuela',
|
||||
'Vietnam' => 'vietnam',
|
||||
)
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Topics',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of trending topics to return. Max 50',
|
||||
'defaultValue' => 20,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 900; // 15 mins
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$updated = $html->find('time', 0)->datetime;
|
||||
$trends = $html->find('div.trends', 0);
|
||||
|
||||
$limit = $this->getInput('limit');
|
||||
|
||||
if ($limit > 50 || $limit < 1) {
|
||||
$limit = 50;
|
||||
}
|
||||
|
||||
foreach($trends->find('ol.items > li') as $index => $li) {
|
||||
$number = $index + 1;
|
||||
|
||||
$item = array();
|
||||
|
||||
$name = rtrim($li->find('span.trend.name', 0)->plaintext, ' ');
|
||||
$tweets = str_replace(' tweets', '', $li->find('span.tweets', 0)->plaintext);
|
||||
$tweets = str_replace('<', '', $tweets);
|
||||
|
||||
$item['title'] = '#' . $number . ' - ' . $name . ' (' . $tweets . ' tweets)';
|
||||
$item['uri'] = 'https://twitter.com/search?q=' . rawurlencode($name);
|
||||
|
||||
if ($tweets === '10K') {
|
||||
$tweets = 'less than 10K';
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<strong>Rank</strong><br>
|
||||
<p>{$number}</p>
|
||||
<Strong>Topic</strong><br>
|
||||
<p>{$name}</p>
|
||||
<Strong>Tweets</strong><br>
|
||||
<p>{$tweets}</p>
|
||||
EOD;
|
||||
$item['timestamp'] = $updated;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('country'))) {
|
||||
return self::URI . '/' . $this->getInput('country');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (!is_null($this->getInput('country'))) {
|
||||
$parameters = $this->getParameters();
|
||||
$values = array_flip($parameters[0]['country']['values']);
|
||||
|
||||
return $values[$this->getInput('country')] . ' - TwitScoop';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -20,7 +20,9 @@ class TwitchBridge extends BridgeAbstract {
|
||||
'All' => 'all',
|
||||
'Archive' => 'archive',
|
||||
'Highlights' => 'highlight',
|
||||
'Uploads' => 'upload'
|
||||
'Uploads' => 'upload',
|
||||
'Past Premieres' => 'past_premiere',
|
||||
'Premiere Uploads' => 'premiere_upload'
|
||||
),
|
||||
'defaultValue' => 'archive'
|
||||
)
|
||||
@@ -32,43 +34,90 @@ class TwitchBridge extends BridgeAbstract {
|
||||
*/
|
||||
const CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
|
||||
|
||||
const API_ENDPOINT = 'https://gql.twitch.tv/gql';
|
||||
const BROADCAST_TYPES = array(
|
||||
'all' => array(
|
||||
'ARCHIVE',
|
||||
'HIGHLIGHT',
|
||||
'UPLOAD',
|
||||
'PAST_PREMIERE',
|
||||
'PREMIERE_UPLOAD'
|
||||
),
|
||||
'archive' => 'ARCHIVE',
|
||||
'highlight' => 'HIGHLIGHT',
|
||||
'upload' => 'UPLOAD',
|
||||
'past_premiere' => 'PAST_PREMIERE',
|
||||
'premiere_upload' => 'PREMIERE_UPLOAD'
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
// get channel user
|
||||
$query_data = array(
|
||||
'login' => $this->getInput('channel')
|
||||
$query = <<<'EOD'
|
||||
query VODList($channel: String!, $types: [BroadcastType!]) {
|
||||
user(login: $channel) {
|
||||
displayName
|
||||
videos(types: $types, sort: TIME) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
publishedAt
|
||||
lengthSeconds
|
||||
viewCount
|
||||
thumbnailURLs(width: 640, height: 360)
|
||||
previewThumbnailURL(width: 640, height: 360)
|
||||
description
|
||||
tags
|
||||
contentTags {
|
||||
isLanguageTag
|
||||
localizedName
|
||||
}
|
||||
game {
|
||||
displayName
|
||||
}
|
||||
moments(momentRequestType: VIDEO_CHAPTER_MARKERS) {
|
||||
edges {
|
||||
node {
|
||||
description
|
||||
positionMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOD;
|
||||
$variables = array(
|
||||
'channel' => $this->getInput('channel'),
|
||||
'types' => self::BROADCAST_TYPES[$this->getInput('type')]
|
||||
);
|
||||
$users = $this->apiGet('users', $query_data)->users;
|
||||
if(count($users) === 0)
|
||||
returnClientError('User "'
|
||||
. $this->getInput('channel')
|
||||
. '" could not be found');
|
||||
$user = $users[0];
|
||||
$data = $this->apiRequest($query, $variables);
|
||||
|
||||
// get video list
|
||||
$query_endpoint = 'channels/' . $user->_id . '/videos';
|
||||
$query_data = array(
|
||||
'broadcast_type' => $this->getInput('type'),
|
||||
'limit' => 10
|
||||
);
|
||||
$videos = $this->apiGet($query_endpoint, $query_data)->videos;
|
||||
$user = $data->user;
|
||||
foreach($user->videos->edges as $edge) {
|
||||
$video = $edge->node;
|
||||
|
||||
$url = 'https://www.twitch.tv/videos/' . $video->id;
|
||||
|
||||
foreach($videos as $video) {
|
||||
$item = array(
|
||||
'uri' => $video->url,
|
||||
'uri' => $url,
|
||||
'title' => $video->title,
|
||||
'timestamp' => $video->published_at,
|
||||
'author' => $video->channel->display_name,
|
||||
'timestamp' => $video->publishedAt,
|
||||
'author' => $user->displayName,
|
||||
);
|
||||
|
||||
// Add categories for tags and played game
|
||||
$item['categories'] = array_filter(explode(' ', $video->tag_list));
|
||||
if(!empty($video->game))
|
||||
$item['categories'][] = $video->game;
|
||||
$item['categories'] = $video->tags;
|
||||
if(!is_null($video->game))
|
||||
$item['categories'][] = $video->game->displayName;
|
||||
foreach($video->contentTags as $tag)
|
||||
if(!$tag->isLanguageTag)
|
||||
$item['categories'][] = $tag->localizedName;
|
||||
|
||||
// Add enclosures for thumbnails from a few points in the video
|
||||
$item['enclosures'] = array();
|
||||
foreach($video->thumbnails->large as $thumbnail)
|
||||
$item['enclosures'][] = $thumbnail->url;
|
||||
// Thumbnail list has duplicate entries sometimes so remove those
|
||||
$item['enclosures'] = array_unique($video->thumbnailURLs);
|
||||
|
||||
/*
|
||||
* Content format example:
|
||||
@@ -86,44 +135,45 @@ class TwitchBridge extends BridgeAbstract {
|
||||
*
|
||||
*/
|
||||
$item['content'] = '<p><a href="'
|
||||
. $video->url
|
||||
. $url
|
||||
. '"><img src="'
|
||||
. $video->preview->large
|
||||
. $video->previewThumbnailURL
|
||||
. '" /></a></p><p>'
|
||||
. $video->description_html
|
||||
. $video->description // in markdown format
|
||||
. '</p><p><b>Duration:</b> '
|
||||
. $this->formatTimestampTime($video->length)
|
||||
. $this->formatTimestampTime($video->lengthSeconds)
|
||||
. '<br/><b>Views:</b> '
|
||||
. $video->views
|
||||
. $video->viewCount
|
||||
. '</p>';
|
||||
|
||||
// Add played games list to content
|
||||
$video_id = trim($video->_id, 'v'); // _id gives 'v1234' but API wants '1234'
|
||||
$markers = $this->apiGet('videos/' . $video_id . '/markers')->markers;
|
||||
$item['content'] .= '<p><b>Played games:</b></b><ul><li><a href="'
|
||||
. $video->url
|
||||
. '">00:00:00</a> - '
|
||||
. $video->game
|
||||
. '</li>';
|
||||
if(isset($markers->game_changes)) {
|
||||
usort($markers->game_changes, function($a, $b) {
|
||||
return $a->time - $b->time;
|
||||
});
|
||||
foreach($markers->game_changes as $game_change) {
|
||||
$item['categories'][] = $game_change->label;
|
||||
$item['content'] .= '<p><b>Played games:</b><ul>';
|
||||
if(count($video->moments->edges) > 0) {
|
||||
foreach($video->moments->edges as $edge) {
|
||||
$moment = $edge->node;
|
||||
|
||||
$item['categories'][] = $moment->description;
|
||||
$item['content'] .= '<li><a href="'
|
||||
. $video->url
|
||||
. $url
|
||||
. '?t='
|
||||
. $this->formatQueryTime($game_change->time)
|
||||
. $this->formatQueryTime($moment->positionMilliseconds / 1000)
|
||||
. '">'
|
||||
. $this->formatTimestampTime($game_change->time)
|
||||
. $this->formatTimestampTime($moment->positionMilliseconds / 1000)
|
||||
. '</a> - '
|
||||
. $game_change->label
|
||||
. $moment->description
|
||||
. '</li>';
|
||||
}
|
||||
} else {
|
||||
$item['content'] .= '<li><a href="'
|
||||
. $url
|
||||
. '">00:00:00</a> - '
|
||||
. ($video->game ? $video->game->displayName : 'No Game')
|
||||
. '</li>';
|
||||
}
|
||||
$item['content'] .= '</ul></p>';
|
||||
|
||||
$item['categories'] = array_unique($item['categories']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -144,25 +194,37 @@ class TwitchBridge extends BridgeAbstract {
|
||||
$seconds % 60);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ideally the new 'helix' API should be used as v5/'kraken' is deprecated.
|
||||
* The new API however still misses many features (markers, played game..) of
|
||||
* the old one, so let's use the old one for as long as it's available.
|
||||
*/
|
||||
private function apiGet($endpoint, $query_data = array()) {
|
||||
$query_data['api_version'] = 5;
|
||||
$url = 'https://api.twitch.tv/kraken/'
|
||||
. $endpoint
|
||||
. '?'
|
||||
. http_build_query($query_data);
|
||||
// GraphQL: https://graphql.org/
|
||||
// Tool for developing/testing queries: https://github.com/skevy/graphiql-app
|
||||
private function apiRequest($query, $variables) {
|
||||
$request = array(
|
||||
'query' => $query,
|
||||
'variables' => $variables
|
||||
);
|
||||
$header = array(
|
||||
'Client-ID: ' . self::CLIENT_ID
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => json_encode($request)
|
||||
);
|
||||
|
||||
$data = json_decode(getContents($url, $header))
|
||||
or returnServerError('API request to "' . $url . '" failed.');
|
||||
Debug::log("Sending GraphQL query:\n" . $query);
|
||||
Debug::log("Sending GraphQL variables:\n"
|
||||
. json_encode($variables, JSON_PRETTY_PRINT));
|
||||
|
||||
return $data;
|
||||
$response = json_decode(getContents(self::API_ENDPOINT, $header, $opts))
|
||||
or returnServerError('API request to "' . self::API_ENDPOINT . '" failed.');
|
||||
|
||||
Debug::log("Got GraphQL response:\n"
|
||||
. json_encode($response, JSON_PRETTY_PRINT));
|
||||
|
||||
if(isset($response->errors)) {
|
||||
$messages = array_column($response->errors, 'message');
|
||||
returnServerError('API error(s): ' . implode("\n", $messages));
|
||||
}
|
||||
|
||||
return $response->data;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
@@ -2,6 +2,9 @@
|
||||
class TwitterBridge extends BridgeAbstract {
|
||||
const NAME = 'Twitter Bridge';
|
||||
const URI = 'https://twitter.com/';
|
||||
const API_URI = 'https://api.twitter.com';
|
||||
const GUEST_TOKEN_USES = 100;
|
||||
const GUEST_TOKEN_EXPIRY = 300; // 5min
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'returns tweets';
|
||||
const MAINTAINER = 'pmaziere';
|
||||
@@ -72,6 +75,12 @@ EOD
|
||||
'required' => false,
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Hide retweets'
|
||||
),
|
||||
'nopinned' => array(
|
||||
'name' => 'Without pinned tweet',
|
||||
'required' => false,
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Hide pinned tweet'
|
||||
)
|
||||
),
|
||||
'By list' => array(
|
||||
@@ -92,6 +101,20 @@ EOD
|
||||
'required' => false,
|
||||
'title' => 'Specify term to search for'
|
||||
)
|
||||
),
|
||||
'By list ID' => array(
|
||||
'listid' => array(
|
||||
'name' => 'List ID',
|
||||
'exampleValue' => '31748',
|
||||
'required' => true,
|
||||
'title' => 'Insert the list id'
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Filter',
|
||||
'exampleValue' => '#rss-bridge',
|
||||
'required' => false,
|
||||
'title' => 'Specify term to search for'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -142,6 +165,8 @@ EOD
|
||||
break;
|
||||
case 'By list':
|
||||
return $this->getInput('list') . ' - Twitter list by ' . $this->getInput('user');
|
||||
case 'By list ID':
|
||||
return 'Twitter List #' . $this->getInput('listid');
|
||||
default: return parent::getName();
|
||||
}
|
||||
return 'Twitter ' . $specific . $this->getInput($param);
|
||||
@@ -164,26 +189,58 @@ EOD
|
||||
. urlencode($this->getInput('user'))
|
||||
. '/lists/'
|
||||
. str_replace(' ', '-', strtolower($this->getInput('list')));
|
||||
case 'By list ID':
|
||||
return self::URI
|
||||
. 'i/lists/'
|
||||
. urlencode($this->getInput('listid'));
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
private function getApiURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
return self::API_URI
|
||||
. '/2/search/adaptive.json?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
. '&tweet_mode=extended&tweet_search_mode=live';
|
||||
case 'By username':
|
||||
// use search endpoint if without replies or without retweets enabled
|
||||
if ($this->getInput('noretweet') || $this->getInput('norep')) {
|
||||
$query = 'from:' . $this->getInput('u');
|
||||
// Twitter's from: search excludes retweets by default
|
||||
if (!$this->getInput('noretweet')) $query .= ' include:nativeretweets';
|
||||
if ($this->getInput('norep')) $query .= ' exclude:replies';
|
||||
return self::API_URI
|
||||
. '/2/search/adaptive.json?q='
|
||||
. urlencode($query)
|
||||
. '&tweet_mode=extended&tweet_search_mode=live';
|
||||
} else {
|
||||
return self::API_URI
|
||||
. '/2/timeline/profile/'
|
||||
. $this->getRestId($this->getInput('u'))
|
||||
. '.json?tweet_mode=extended';
|
||||
}
|
||||
case 'By list':
|
||||
return self::API_URI
|
||||
. '/2/timeline/list.json?list_id='
|
||||
. $this->getListId($this->getInput('user'), $this->getInput('list'))
|
||||
. '&tweet_mode=extended';
|
||||
case 'By list ID':
|
||||
return self::API_URI
|
||||
. '/2/timeline/list.json?list_id='
|
||||
. $this->getInput('listid')
|
||||
. '&tweet_mode=extended';
|
||||
default: returnServerError('Invalid query context !');
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$page = $this->getURI();
|
||||
$data = json_decode($this->getApiContents($this->getApiURI()));
|
||||
|
||||
$header = array(
|
||||
'User-Agent: Mozilla/5.0 (Windows NT 9.0; WOW64; Trident/7.0; rv:11.0) like Gecko'
|
||||
);
|
||||
|
||||
if(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo'))) {
|
||||
$cookies = $this->getCookies($page);
|
||||
$html = getSimpleHTMLDOM($page, array_merge($header, array("Cookie: $cookies")));
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($page, $header, array(CURLOPT_COOKIEFILE => ''));
|
||||
}
|
||||
|
||||
if(!$html) {
|
||||
if(!$data) {
|
||||
switch($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
returnServerError('No results for this query.');
|
||||
@@ -196,75 +253,121 @@ EOD
|
||||
|
||||
$hidePictures = $this->getInput('nopic');
|
||||
|
||||
foreach($html->find('div.js-stream-tweet') as $tweet) {
|
||||
$promotedTweetIds = array_reduce($data->timeline->instructions[0]->addEntries->entries, function($carry, $entry) {
|
||||
if (!isset($entry->content->item)) {
|
||||
return $carry;
|
||||
}
|
||||
$tweet = $entry->content->item->content->tweet;
|
||||
if (isset($tweet->promotedMetadata)) {
|
||||
$carry[] = $tweet->id;
|
||||
}
|
||||
return $carry;
|
||||
}, array());
|
||||
|
||||
// Skip retweets?
|
||||
if($this->getInput('noretweet')
|
||||
&& $tweet->find('div.context span.js-retweet-text a', 0)) {
|
||||
$hidePinned = $this->getInput('nopinned');
|
||||
if ($hidePinned) {
|
||||
$pinnedTweetId = null;
|
||||
if (isset($data->timeline->instructions[1]) && isset($data->timeline->instructions[1]->pinEntry)) {
|
||||
$pinnedTweetId = $data->timeline->instructions[1]->pinEntry->entry->content->item->content->tweet->id;
|
||||
}
|
||||
}
|
||||
|
||||
$tweets = array();
|
||||
|
||||
// Extract tweets from timeline property when in username mode
|
||||
// This fixes number of issues:
|
||||
// * If there's a retweet of a quote tweet, the quoted tweet will not appear in results (since it wasn't retweeted directly)
|
||||
// * Pinned tweets do not get stuck at the bottom
|
||||
if ($this->queriedContext === 'By username') {
|
||||
foreach($data->timeline->instructions[0]->addEntries->entries as $tweet) {
|
||||
if (!isset($tweet->content->item)) continue;
|
||||
$tweetId = $tweet->content->item->content->tweet->id;
|
||||
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
|
||||
if (!$selectedTweet) continue;
|
||||
// If this is a retweet, it will contain shorter text and will point to the original full tweet (retweeted_status_id_str).
|
||||
// Let's use the original tweet text.
|
||||
if (isset($selectedTweet->retweeted_status_id_str)) {
|
||||
$tweetId = $selectedTweet->retweeted_status_id_str;
|
||||
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
|
||||
if (!$selectedTweet) continue;
|
||||
}
|
||||
// use $tweetId as key to avoid duplicates (e.g. user retweeting their own tweet)
|
||||
$tweets[$tweetId] = $selectedTweet;
|
||||
}
|
||||
} else {
|
||||
foreach($data->globalObjects->tweets as $tweet) {
|
||||
$tweets[] = $tweet;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($tweets as $tweet) {
|
||||
|
||||
/* Debug::log('>>> ' . json_encode($tweet)); */
|
||||
// Skip spurious retweets
|
||||
if (isset($tweet->retweeted_status_id_str) && substr($tweet->full_text, 0, 4) === 'RT @') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove 'invisible' content
|
||||
foreach($tweet->find('.invisible') as $invisible) {
|
||||
$invisible->outertext = '';
|
||||
// Skip promoted tweets
|
||||
if (in_array($tweet->id_str, $promotedTweetIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip protmoted tweets
|
||||
$heading = $tweet->previousSibling();
|
||||
if(!is_null($heading) &&
|
||||
$heading->getAttribute('class') === 'promoted-tweet-heading'
|
||||
) {
|
||||
// Skip pinned tweet
|
||||
if ($hidePinned && $tweet->id_str === $pinnedTweetId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
// extract username and sanitize
|
||||
$item['username'] = htmlspecialchars_decode($tweet->getAttribute('data-screen-name'), ENT_QUOTES);
|
||||
// extract fullname (pseudonym)
|
||||
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
|
||||
// get author
|
||||
$user_info = $this->getUserInformation($tweet->user_id_str, $data->globalObjects);
|
||||
|
||||
$item['username'] = $user_info->screen_name;
|
||||
$item['fullname'] = $user_info->name;
|
||||
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
||||
if($rt = $tweet->find('div.context span.js-retweet-text a', 0)) {
|
||||
$item['author'] .= ' RT: @' . $rt->plaintext;
|
||||
if (null !== $this->getInput('u') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
|
||||
$item['author'] .= ' RT: @' . $this->getInput('u');
|
||||
}
|
||||
// get avatar link
|
||||
$item['avatar'] = $tweet->find('img', 0)->src;
|
||||
// get TweetID
|
||||
$item['id'] = $tweet->getAttribute('data-tweet-id');
|
||||
// get tweet link
|
||||
$item['uri'] = self::URI . substr($tweet->find('a.js-permalink', 0)->getAttribute('href'), 1);
|
||||
$item['avatar'] = $user_info->profile_image_url_https;
|
||||
|
||||
$item['id'] = $tweet->id_str;
|
||||
$item['uri'] = self::URI . $item['username'] . '/status/' . $item['id'];
|
||||
// extract tweet timestamp
|
||||
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
|
||||
// generate the title
|
||||
$item['title'] = strip_tags($this->fixAnchorSpacing(htmlspecialchars_decode(
|
||||
$tweet->find('p.js-tweet-text', 0), ENT_QUOTES), '<a>'));
|
||||
$item['timestamp'] = $tweet->created_at;
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By list':
|
||||
// Check if filter applies to list (using raw content)
|
||||
if($this->getInput('filter')) {
|
||||
if(stripos($tweet->find('p.js-tweet-text', 0)->plaintext, $this->getInput('filter')) === false) {
|
||||
continue 2; // switch + for-loop!
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Convert plain text URLs into HTML hyperlinks
|
||||
$cleanedTweet = $tweet->full_text;
|
||||
$foundUrls = false;
|
||||
|
||||
if (isset($tweet->entities->media)) {
|
||||
foreach($tweet->entities->media as $media) {
|
||||
$cleanedTweet = str_replace($media->url,
|
||||
'<a href="' . $media->expanded_url . '">' . $media->display_url . '</a>',
|
||||
$cleanedTweet);
|
||||
$foundUrls = true;
|
||||
}
|
||||
}
|
||||
if (isset($tweet->entities->urls)) {
|
||||
foreach($tweet->entities->urls as $url) {
|
||||
$cleanedTweet = str_replace($url->url,
|
||||
'<a href="' . $url->expanded_url . '">' . $url->display_url . '</a>',
|
||||
$cleanedTweet);
|
||||
$foundUrls = true;
|
||||
}
|
||||
}
|
||||
if ($foundUrls === false) {
|
||||
// fallback to regex'es
|
||||
$reg_ex = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/';
|
||||
if(preg_match($reg_ex, $tweet->full_text, $url)) {
|
||||
$cleanedTweet = preg_replace($reg_ex,
|
||||
"<a href='{$url[0]}' target='_blank'>{$url[0]}</a> ",
|
||||
$cleanedTweet);
|
||||
}
|
||||
}
|
||||
// generate the title
|
||||
$item['title'] = strip_tags($cleanedTweet);
|
||||
|
||||
$this->processContentLinks($tweet);
|
||||
$this->processEmojis($tweet);
|
||||
|
||||
// get tweet text
|
||||
$cleanedTweet = str_replace(
|
||||
'href="/',
|
||||
'href="' . self::URI,
|
||||
$tweet->find('p.js-tweet-text', 0)->innertext
|
||||
);
|
||||
|
||||
// fix anchors missing spaces in-between
|
||||
$cleanedTweet = $this->fixAnchorSpacing($cleanedTweet);
|
||||
|
||||
// Add picture to content
|
||||
// Add avatar
|
||||
$picture_html = '';
|
||||
if(!$hidePictures) {
|
||||
$picture_html = <<<EOD
|
||||
@@ -278,31 +381,79 @@ EOD
|
||||
EOD;
|
||||
}
|
||||
|
||||
// Add embeded image to content
|
||||
$image_html = '';
|
||||
$images = $this->getImageURI($tweet);
|
||||
if(!$this->getInput('noimg') && !is_null($images)) {
|
||||
// Get images
|
||||
$media_html = '';
|
||||
if(isset($tweet->extended_entities->media) && !$this->getInput('noimg')) {
|
||||
foreach($tweet->extended_entities->media as $media) {
|
||||
switch($media->type) {
|
||||
case 'photo':
|
||||
$image = $media->media_url_https . '?name=orig';
|
||||
$display_image = $media->media_url_https;
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $image;
|
||||
|
||||
foreach ($images as $image) {
|
||||
|
||||
// Set image scaling
|
||||
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $image_orig;
|
||||
|
||||
$image_html .= <<<EOD
|
||||
<a href="{$image_orig}">
|
||||
$media_html .= <<<EOD
|
||||
<a href="{$image}">
|
||||
<img
|
||||
style="align:top; max-width:558px; border:1px solid black;"
|
||||
src="{$image_thumb}" />
|
||||
referrerpolicy="no-referrer"
|
||||
src="{$display_image}" />
|
||||
</a>
|
||||
EOD;
|
||||
break;
|
||||
case 'video':
|
||||
case 'animated_gif':
|
||||
if(isset($media->video_info)) {
|
||||
$link = $media->expanded_url;
|
||||
$poster = $media->media_url_https;
|
||||
$video = null;
|
||||
$maxBitrate = -1;
|
||||
foreach($media->video_info->variants as $variant) {
|
||||
$bitRate = isset($variant->bitrate) ? $variant->bitrate : -100;
|
||||
if ($bitRate > $maxBitrate) {
|
||||
$maxBitrate = $bitRate;
|
||||
$video = $variant->url;
|
||||
}
|
||||
}
|
||||
if(!is_null($video)) {
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $video;
|
||||
$item['enclosures'][] = $poster;
|
||||
|
||||
$media_html .= <<<EOD
|
||||
<a href="{$link}">Video</a>
|
||||
<video
|
||||
style="align:top; max-width:558px; border:1px solid black;"
|
||||
referrerpolicy="no-referrer"
|
||||
src="{$video}" poster="{$poster}" />
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug::log('Missing support for media type: ' . $media->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add content
|
||||
switch($this->queriedContext) {
|
||||
case 'By list':
|
||||
case 'By list ID':
|
||||
// Check if filter applies to list (using raw content)
|
||||
if($this->getInput('filter')) {
|
||||
if(stripos($cleanedTweet, $this->getInput('filter')) === false) {
|
||||
continue 2; // switch + for-loop!
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'By username':
|
||||
if ($this->getInput('noretweet') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
|
||||
continue 2; // switch + for-loop!
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<div style="display: inline-block; vertical-align: top;">
|
||||
{$picture_html}
|
||||
@@ -311,155 +462,157 @@ EOD;
|
||||
<blockquote>{$cleanedTweet}</blockquote>
|
||||
</div>
|
||||
<div style="display: block; vertical-align: top;">
|
||||
<blockquote>{$image_html}</blockquote>
|
||||
<blockquote>{$media_html}</blockquote>
|
||||
</div>
|
||||
EOD;
|
||||
|
||||
// add quoted tweet
|
||||
$quotedTweet = $tweet->find('div.QuoteTweet', 0);
|
||||
if($quotedTweet) {
|
||||
// get tweet text
|
||||
$cleanedQuotedTweet = str_replace(
|
||||
'href="/',
|
||||
'href="' . self::URI,
|
||||
$quotedTweet->find('div.tweet-text', 0)->innertext
|
||||
);
|
||||
|
||||
$this->processContentLinks($quotedTweet);
|
||||
$this->processEmojis($quotedTweet);
|
||||
|
||||
// Add embeded image to content
|
||||
$quotedImage_html = '';
|
||||
$quotedImages = $this->getQuotedImageURI($tweet);
|
||||
|
||||
if(!$this->getInput('noimg') && !is_null($quotedImages)) {
|
||||
|
||||
foreach ($quotedImages as $image) {
|
||||
|
||||
// Set image scaling
|
||||
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $image_orig;
|
||||
|
||||
$quotedImage_html .= <<<EOD
|
||||
<a href="{$image_orig}">
|
||||
<img
|
||||
style="align:top; max-width:558px; border:1px solid black;"
|
||||
src="{$image_thumb}" />
|
||||
</a>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
{$item['content']}
|
||||
<hr>
|
||||
<div style="display: inline-block; vertical-align: top;">
|
||||
<blockquote>{$cleanedQuotedTweet}</blockquote>
|
||||
</div>
|
||||
<div style="display: block; vertical-align: top;">
|
||||
<blockquote>{$quotedImage_html}</blockquote>
|
||||
</div>
|
||||
EOD;
|
||||
}
|
||||
$item['content'] = htmlspecialchars_decode($item['content'], ENT_QUOTES);
|
||||
|
||||
// put out
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
usort($this->items, array('TwitterBridge', 'compareTweetId'));
|
||||
}
|
||||
|
||||
private function processEmojis($tweet){
|
||||
// process emojis (reduce size)
|
||||
foreach($tweet->find('img.Emoji') as $img) {
|
||||
$img->style .= ' height: 1em;';
|
||||
private static function compareTweetId($tweet1, $tweet2) {
|
||||
return (intval($tweet1['id']) < intval($tweet2['id']) ? 1 : -1);
|
||||
}
|
||||
|
||||
//The aim of this function is to get an API key and a guest token
|
||||
//This function takes 2 requests, and therefore is cached
|
||||
private function getApiKey() {
|
||||
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$r_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$r_cache->setScope(get_called_class());
|
||||
$r_cache->setKey(array('refresh'));
|
||||
$data = $r_cache->loadData();
|
||||
|
||||
$refresh = null;
|
||||
if($data === null) {
|
||||
$refresh = time();
|
||||
$r_cache->saveData($refresh);
|
||||
} else {
|
||||
$refresh = $data;
|
||||
}
|
||||
}
|
||||
|
||||
private function processContentLinks($tweet){
|
||||
// processing content links
|
||||
foreach($tweet->find('a') as $link) {
|
||||
if($link->hasAttribute('data-expanded-url')) {
|
||||
$link->href = $link->getAttribute('data-expanded-url');
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope(get_called_class());
|
||||
$cache->setKey(array('api_key'));
|
||||
$data = $cache->loadData();
|
||||
|
||||
$apiKey = null;
|
||||
if($data === null || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY) {
|
||||
$twitterPage = getContents('https://twitter.com');
|
||||
|
||||
$jsLink = false;
|
||||
$jsMainRegexArray = array(
|
||||
'/(https:\/\/abs\.twimg\.com\/responsive-web\/web\/main\.[^\.]+\.js)/m',
|
||||
'/(https:\/\/abs\.twimg\.com\/responsive-web\/web_legacy\/main\.[^\.]+\.js)/m',
|
||||
'/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web\/main\.[^\.]+\.js)/m',
|
||||
'/(https:\/\/abs\.twimg\.com\/responsive-web\/client-web-legacy\/main\.[^\.]+\.js)/m',
|
||||
);
|
||||
foreach ($jsMainRegexArray as $jsMainRegex) {
|
||||
if (preg_match_all($jsMainRegex, $twitterPage, $jsMainMatches, PREG_SET_ORDER, 0)) {
|
||||
$jsLink = $jsMainMatches[0][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$link->removeAttribute('data-expanded-url');
|
||||
$link->removeAttribute('data-query-source');
|
||||
$link->removeAttribute('rel');
|
||||
$link->removeAttribute('class');
|
||||
$link->removeAttribute('target');
|
||||
$link->removeAttribute('title');
|
||||
if (!$jsLink) {
|
||||
returnServerError('Could not locate main.js link');
|
||||
}
|
||||
|
||||
$jsContent = getContents($jsLink);
|
||||
$apiKeyRegex = '/([a-zA-Z0-9]{59}%[a-zA-Z0-9]{44})/m';
|
||||
preg_match_all($apiKeyRegex, $jsContent, $apiKeyMatches, PREG_SET_ORDER, 0);
|
||||
$apiKey = $apiKeyMatches[0][0];
|
||||
$cache->saveData($apiKey);
|
||||
} else {
|
||||
$apiKey = $data;
|
||||
}
|
||||
|
||||
$cacheFac2 = new CacheFactory();
|
||||
$cacheFac2->setWorkingDir(PATH_LIB_CACHES);
|
||||
$gt_cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$gt_cache->setScope(get_called_class());
|
||||
$gt_cache->setKey(array('guest_token'));
|
||||
$guestTokenUses = $gt_cache->loadData();
|
||||
|
||||
$guestToken = null;
|
||||
if($guestTokenUses === null || !is_array($guestTokenUses) || count($guestTokenUses) != 2
|
||||
|| $guestTokenUses[0] <= 0 || (time() - $refresh) > self::GUEST_TOKEN_EXPIRY) {
|
||||
$guestToken = $this->getGuestToken();
|
||||
$gt_cache->saveData(array(self::GUEST_TOKEN_USES, $guestToken));
|
||||
$r_cache->saveData(time());
|
||||
} else {
|
||||
$guestTokenUses[0] -= 1;
|
||||
$gt_cache->saveData($guestTokenUses);
|
||||
$guestToken = $guestTokenUses[1];
|
||||
}
|
||||
|
||||
return array($apiKey, $guestToken);
|
||||
|
||||
}
|
||||
|
||||
private function fixAnchorSpacing($content){
|
||||
// fix anchors missing spaces in-between
|
||||
return str_replace(
|
||||
'<a',
|
||||
' <a',
|
||||
$content
|
||||
);
|
||||
// Get a guest token. This is different to an API key,
|
||||
// and it seems to change more regularly than the API key.
|
||||
private function getGuestToken() {
|
||||
$pageContent = getContents('https://twitter.com', array(), array(), true);
|
||||
|
||||
$guestTokenRegex = '/gt=([0-9]*)/m';
|
||||
preg_match_all($guestTokenRegex, $pageContent['header'], $guestTokenMatches, PREG_SET_ORDER, 0);
|
||||
if (!$guestTokenMatches)
|
||||
preg_match_all($guestTokenRegex, $pageContent['content'], $guestTokenMatches, PREG_SET_ORDER, 0);
|
||||
if (!$guestTokenMatches) returnServerError('Could not parse guest token');
|
||||
$guestToken = $guestTokenMatches[0][1];
|
||||
return $guestToken;
|
||||
}
|
||||
|
||||
private function getImageURI($tweet){
|
||||
// Find media in tweet
|
||||
$images = array();
|
||||
private function getApiContents($uri) {
|
||||
$apiKeys = $this->getApiKey();
|
||||
$headers = array('authorization: Bearer ' . $apiKeys[0],
|
||||
'x-guest-token: ' . $apiKeys[1],
|
||||
);
|
||||
return getContents($uri, $headers);
|
||||
}
|
||||
|
||||
$container = $tweet->find('div.AdaptiveMedia-container', 0);
|
||||
private function getRestId($username) {
|
||||
$searchparams = urlencode('{"screen_name":"' . strtolower($username) . '", "withHighlightedLabel":true}');
|
||||
$searchURL = self::API_URI . '/graphql/-xfUfZsnR_zqjFd-IfrN5A/UserByScreenName?variables=' . $searchparams;
|
||||
$searchResult = $this->getApiContents($searchURL);
|
||||
$searchResult = json_decode($searchResult);
|
||||
return $searchResult->data->user->rest_id;
|
||||
}
|
||||
|
||||
if($container && $container->find('img', 0)) {
|
||||
foreach ($container->find('img') as $img) {
|
||||
$images[] = $img->src;
|
||||
private function getListId($username, $listName) {
|
||||
$searchparams = urlencode('{"screenName":"'
|
||||
. strtolower($username)
|
||||
. '", "listSlug": "'
|
||||
. $listName
|
||||
. '", "withHighlightedLabel":false}');
|
||||
$searchURL = self::API_URI . '/graphql/ErWsz9cObLel1BF-HjuBlA/ListBySlug?variables=' . $searchparams;
|
||||
$searchResult = $this->getApiContents($searchURL);
|
||||
$searchResult = json_decode($searchResult);
|
||||
return $searchResult->data->user_by_screen_name->list->id_str;
|
||||
}
|
||||
|
||||
private function getUserInformation($userId, $apiData) {
|
||||
foreach($apiData->users as $user) {
|
||||
if($user->id_str == $userId) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($images)) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getQuotedImageURI($tweet){
|
||||
// Find media in tweet
|
||||
$images = array();
|
||||
|
||||
$container = $tweet->find('div.QuoteMedia-container', 0);
|
||||
|
||||
if($container && $container->find('img', 0)) {
|
||||
foreach ($container->find('img') as $img) {
|
||||
$images[] = $img->src;
|
||||
}
|
||||
private function getTweet($tweetId, $apiData) {
|
||||
if (property_exists($apiData->tweets, $tweetId)) {
|
||||
return $apiData->tweets->$tweetId;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($images)) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCookies($pageURL){
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'follow_location' => false
|
||||
)
|
||||
)
|
||||
);
|
||||
$a = file_get_contents($pageURL, 0, $ctx);
|
||||
|
||||
//First request to get the cookie
|
||||
$cookies = '';
|
||||
foreach($http_response_header as $hdr) {
|
||||
if(stripos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($cookies, 2);
|
||||
}
|
||||
}
|
||||
|
71
bridges/UnraidCommunityApplicationsBridge.php
Normal file
71
bridges/UnraidCommunityApplicationsBridge.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
class UnraidCommunityApplicationsBridge extends BridgeAbstract {
|
||||
const NAME = 'Unraid Community Applications';
|
||||
const URI = 'https://forums.unraid.net/topic/38582-plug-in-community-applications/';
|
||||
const DESCRIPTION = 'Fetches the latest fifteen new apps/plugins from Unraid Community Applications';
|
||||
const MAINTAINER = 'Paroleen';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const APPSURI = 'https://raw.githubusercontent.com/Squidly271/AppFeed/master/applicationFeed.json';
|
||||
|
||||
private $apps = array();
|
||||
|
||||
private function fetchApps() {
|
||||
Debug::log('Fetching all applications/plugins');
|
||||
$this->apps = getContents(self::APPSURI)
|
||||
or returnServerError('Could not fetch JSON for apps.');
|
||||
$this->apps = json_decode($this->apps, true)['applist'];
|
||||
}
|
||||
|
||||
private function sortApps() {
|
||||
Debug::log('Sorting applications/plugins');
|
||||
usort($this->apps, function($app1, $app2) {
|
||||
return $app1['FirstSeen'] < $app2['FirstSeen'] ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->fetchApps();
|
||||
$this->sortApps();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach($this->apps as $app) {
|
||||
if(!array_key_exists('Language', $app)) {
|
||||
$item = array();
|
||||
$item['title'] = $app['Name'];
|
||||
$item['timestamp'] = $app['FirstSeen'];
|
||||
$item['author'] = explode('\'', $app['Repo'])[0];
|
||||
$item['categories'] = explode(' ', $app['Category']);
|
||||
$item['content'] = '';
|
||||
|
||||
if(array_key_exists('Icon', $app))
|
||||
$item['content'] .= '<img style="width: 64px" src="'
|
||||
. $app['Icon']
|
||||
. '">';
|
||||
|
||||
if(array_key_exists('Overview', $app))
|
||||
$item['content'] .= '<p>'
|
||||
. $app['Overview']
|
||||
. '</p>';
|
||||
|
||||
if(array_key_exists('Project', $app))
|
||||
$item['uri'] = $app['Project'];
|
||||
|
||||
if(array_key_exists('Registry', $app))
|
||||
$item['content'] .= '<br><a href="'
|
||||
. $app['Registry']
|
||||
. '">Docker Hub</a>';
|
||||
|
||||
if(array_key_exists('Support', $app))
|
||||
$item['content'] .= '<br><a href="'
|
||||
. $app['Support']
|
||||
. '">Support</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(count($this->items) >= 15)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user